從spring源碼角度分析@Configuration和@Component區別

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章