很多人都用過@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來代替,但是需要使用構造注入或方法注入,如圖:
示例: