很多人都用过@Configuration和@Component,但并不一定了解他们的区别,或者所了解到的区别仅限于理论层面,并不知道真实原因,最近本人在学习spring 5.2.x源码,特记录并分享一下。
首先,我们可以看到@Configuration的代码是这样的:
从图中可以看出,@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时,运行结果如下:
从图中可以看出,A的地址中包含A$$EnhancerBySpringCGLIB,这说明A类是用spring的cglib代理生成的,然后b()中的+++++++只打印一次,且两次打印B的地址都是一样的,说明B是单例的。
然后,当A.class类上面加的注解为@Component时,运行结果如下:
从图中可以看出,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循环时如图:
然后,我们一直走for循环,直到beanName为a时,我们再看截图:
接着,我们继续往下走:
一直执行完第一个for循环,进入第二个for循环,如图:
从几个图中我们可以看出 configClassAttr=full 的才会存入 configBeanDefs 这个map中,在map中的AbstractBeanDefinition才会进入第二个for循环并执行cglib动态代理。进入enhancer.enhance() 后,会调用到ConfigurationClassEnhancer.java的内部类BeanMethodInterceptor的isMatch方法,具体如下图:
然后我们再看下图:
这一段注释说明会处理加了@Bean的注解,具体就不分析了,我们姑且先认为加了@Configuration注解的类中加了@Bean注解的方法只会生成单例对象,当然事实也是如此。
下面我们再看为什么 ConfigurationClassPostProcessor.java的enhanceConfigurationClasses()中A 的 configClassAttr = full,我们在ConfigurationClassUtils.java的checkConfigurationClassCandidate()中断点,下面提供一个调用栈,读者就可以很快知道为什么会在这里断点,如图:
我们可以看到这个方法中有对加了@Configuration注解的处理,当前beanClass为A时,正好满足下图中 if 的逻辑
到这里,相信大家应该明白spring在启动过程中是怎么处理加了@Configuration注解以及@Configuration和@Component的区别了。
最近看了一下官方文档,其实官方文档对此也有说明:
同时,如果不想使用@Configuration,官方 也说明了可以用@Component来代替,但是需要使用构造注入或方法注入,如图:
示例: