很多人都用过@Configuration和@Component,但并不一定了解他们的区别,或者所了解到的区别仅限于理论层面,并不知道真实原因,最近本人在学习​spring 5.2.x​源码,特记录并分享一下。

首先,我们可以看到@Configuration的代码是这样的:

从spring源码角度分析@Configuration和@Component区别_java

从图中可以看出,@Configuration这个类是加上了@Component注解的,所以姑且认为@Component有的功能他都有,但是今天的主题是讨论他们不一样的地方。首先通过代码直接测试,然后再分析源码。测试代码如下:

============================= JavaConfig =====================================

package org.springframework.francis.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan("org.springframework.francis.bean")
public class JavaConfig {

}

================================ A ==========================================

package org.springframework.francis.bean;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class A {
@Bean
public B b() {
System.out.println("+++++++++++++++++");
return new B();
}

@Bean
public C c() {
System.out.println(b());
return new C();
}

}

=================================== B =======================================

package org.springframework.francis.bean;

public class B {
}

================================== C ========================================

package org.springframework.francis.bean;

public class C {
}

================================ Test =======================================

package org.springframework.francis;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.francis.bean.A;
import org.springframework.francis.bean.B;
import org.springframework.francis.config.JavaConfig;


public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
JavaConfig javaConfig = applicationContext.getBean("javaConfig", JavaConfig.class);
System.out.println(javaConfig);

A a = applicationContext.getBean("a", A.class);
System.out.println(a);

B b = applicationContext.getBean("b", B.class);
System.out.println(b);
}

}

当A.class类上面加的注解为@Configuration时,运行结果如下:

从spring源码角度分析@Configuration和@Component区别_spring_02

从图中可以看出,A的地址中包含A$$EnhancerBySpringCGLIB,这说明A类是用spring的cglib代理生成的,然后b()中的+++++++只打印一次,且两次打印B的地址都是一样的,说明B是单例的。

然后,当A.class类上面加的注解为@Component时,运行结果如下:

从spring源码角度分析@Configuration和@Component区别_for循环_03

从图中可以看出,A的地址​并没有​$$EnhancerBySpringCGLIB字样,b()中的+++++++打印了两次,且两次打印B的地址不一样,这说明这个过程中创建了两个B对象。

从两个截图我们可以很明显的看出他们的区别,而且像

@Bean
public C c() {
System.out.println(b());
return new C();
}

这种场景在开发中是比较常见的,比如我们自定义数据源的时候,经常会看到类似的写法:

@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "masterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(masterDataSource());
}

所以遇到类似场景我们就知道该用哪个注解了,而并不是去靠猜和试。而且​也不需要​这么写:

@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}

@Autowired
private DataSource masterDataSource;

@Bean(name = "masterTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(masterDataSource);
}

下面我们从源码角度分析为什么会有这种区别,这时候需要将A.class的注解改为@Configuration,然后debug。

我们先直接断点到ConfigurationClassPostProcessor.java的enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory)方法,第一次走第一个for循环时如图:

从spring源码角度分析@Configuration和@Component区别_java_04

然后,我们一直走for循环,直到beanName为a时,我们再看截图:从spring源码角度分析@Configuration和@Component区别_spring_05

接着,我们继续往下走:

从spring源码角度分析@Configuration和@Component区别_spring_06

一直执行完第一个for循环,进入第二个for循环,如图:

从spring源码角度分析@Configuration和@Component区别_spring_07

从几个图中我们可以看出 configClassAttr=full 的才会存入 configBeanDefs 这个map中,在map中的AbstractBeanDefinition才会进入第二个for循环并执行cglib动态代理。进入enhancer.enhance() 后,会调用到ConfigurationClassEnhancer.java的内部类BeanMethodInterceptor的isMatch方法,具体如下图:

从spring源码角度分析@Configuration和@Component区别_spring_08

然后我们再看下图:

从spring源码角度分析@Configuration和@Component区别_spring_09

这一段注释说明会处理加了@Bean的注解,具体就不分析了,我们姑且先认为加了@Configuration注解的类中加了@Bean注解的方法只会生成单例对象,当然事实也是如此。

下面我们再看为什么 ConfigurationClassPostProcessor.java的enhanceConfigurationClasses()中A 的 configClassAttr = full,我们在ConfigurationClassUtils.java的checkConfigurationClassCandidate()中断点,下面提供一个调用栈,读者就可以很快知道为什么会在这里断点,如图:

从spring源码角度分析@Configuration和@Component区别_java_10

我们可以看到这个方法中有对加了@Configuration注解的处理,当前beanClass为A时,正好满足下图中 if 的逻辑

从spring源码角度分析@Configuration和@Component区别_spring_11

到这里,相信大家应该明白spring在启动过程中是怎么处理加了@Configuration注解以及@Configuration和@Component的区别了。

最近看了一下​​官方文档​​,其实官方文档对此也有说明:

从spring源码角度分析@Configuration和@Component区别_for循环_12

同时,如果不想使用@Configuration,​​官方​​ 也说明了可以用@Component来代替,但是需要使用构造注入或方法注入,如图:

从spring源码角度分析@Configuration和@Component区别_for循环_13

示例:

从spring源码角度分析@Configuration和@Component区别_java_14