程序員必備技能之SpringBoot的自動裝配原理,很詳細,建議收藏!!! 自動裝配原理分析

SpringBoot應該是每個Java程序猿都會使用的基礎框架了,對於SpringBoot的核心內容自動裝配原理的掌握就顯得非常重要了。

自動裝配原理分析

1 理論介紹

SpringBoot通過自動裝配實現了第三方框架系統對象的注入。這種實現機制和我們前面介紹的SPI(服務擴展機制)很相似。

2 源碼分析

2.1 Spring的IoC

SpringBoot的本質是SpringFramework【IoC,AOP】的再次封裝的上層應用框架。

2.2 run方法

我們啓動一個SpringBoot項目,本質上就是執行了啓動類中的主方法,然後調用執行了run方法,那麼run方法到底做了什麼操作呢?我們可以先來分析下:

@SpringBootApplication
@MapperScan("com.bobo.mapper")
public class SpringBootVipDemoApplication {
    public static void main(String[] args) {
        // 基於配置文件的方式
        ApplicationContext ac1 = new ClassPathXmlApplicationContext("");
        // 基於Java配置類的方式
        ApplicationContext ac2 = new AnnotationConfigApplicationContext(SpringBootVipDemoApplication.class);
        // run 方法的返回對象是 ConfigurableApplicationContext 對象
        ConfigurableApplicationContext ac3 = SpringApplication.run(SpringBootVipDemoApplication.class, args);
    }
}
複製代碼

ConfigurableApplicationContext這個對象其實是 ApplicationContext接口的一個子接口

那麼上面的代碼可以調整爲

@SpringBootApplication
@MapperScan("com.bobo.mapper")
public class SpringBootVipDemoApplication {

    public static void main(String[] args) {
        // 基於配置文件的方式
        ApplicationContext ac1 = new ClassPathXmlApplicationContext("");
        // 基於Java配置類的方式
        ApplicationContext ac2 = new AnnotationConfigApplicationContext(SpringBootVipDemoApplication.class);
        // run 方法執行完成後返回的是一個 ApplicationContext 對象
        // 到這兒我們是不是可以猜測 run 方法的執行 其實就是Spring的初始化操作[IoC]
        ApplicationContext ac3 = SpringApplication.run(SpringBootVipDemoApplication.class, args);
    }

}

根據返回結果,我們猜測SpringBoot項目的啓動其實就是Spring的初始化操作【IoC】。

下一步:

下一步:

直接調用:

到這兒,其實我們就可以發現SpringBoot項目的啓動,本質上就是Spring的初始化操作。但是並沒有涉及到SpringBoot的核心裝配。

2.3 @SpringBootApplication

@SpringBootApplication點開後我們能夠發現@SpringBootApplication這個註解其實是一個組合註解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

我們發現@SpringBootApplication註解的前面四個註解是JDK中自動的元註解 (用來修飾註解的註解)

@Target({ElementType.TYPE}) // 表明 修飾的註解的位置 TYPE 表示只能修飾類
@Retention(RetentionPolicy.RUNTIME) // 表明註解的作用域
@Documented // API 文檔抽取的時候會將該註解 抽取到API文檔中
@Inherited  // 表示註解的繼承
複製代碼

還有就是@ComponentScan註解,該註解的作用是用來指定掃描路徑的,如果不指定特定的掃描路徑的話,掃描的路徑是當前修飾的類所在的包及其子包。

@SpringBootConfiguration這個註解的本質其實是@Configuration註解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 上面是3個元註解
// @Configuration 註解修飾的Java類是一個配置類
@Configuration
// @Indexed
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

這樣一來7個註解,咱們清楚了其中的6個註解的作用,而且這6個註解都和SpringBoot的自動裝配是沒有關係的。

2.4 @EnableAutoConfiguration

@EnableAutoConfiguration這個註解就是SpringBoot自動裝配的關鍵。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
複製代碼

我們發現要搞清楚EnableAutoConfiguration註解的關鍵是要弄清楚@Import註解。這個內容我們前面在註解編程發展中有詳細的介紹。AutoConfigurationImportSelector實現了ImportSelector接口,那麼我們清楚只需要關注selectImports方法的返回結果即可

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            // 返回的就是需要註冊到IoC容器中的對象對應的類型的全類路徑名稱的字符串數組
            // ["com.bobo.pojo.User","com.bobo.pojo.Person", ....]
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

我們清楚了該方法的作用就是要返回需要註冊到IoC容器中的對象對應的類型的全類路徑名稱的字符串數組。那麼我接下來分析的關鍵是返回的數據是哪來的?所以呢進入getAutoConfigurationEntry方法中。

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // 獲取註解的屬性信息
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 獲取候選配置信息 加載的是 當前項目的classpath目錄下的 所有的 spring.factories 文件中的 key 爲
            // org.springframework.boot.autoconfigure.EnableAutoConfiguration 的信息
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

先不進入代碼,直接DEBUG調試到 候選配置信息這步。我們發現裏面有很多個Java類。

然後進入getCandidateConfiguration方法中,我們可以發現加載的是 META-INF/spring.factories 文件中的配置信息

然後我們可以驗證,進入到具體的META-INF目錄下查看文件。

最後幾個

在我們的Debug中還有一個配置文件(MyBatisAutoConfiguration)是哪來的呢?

深入源碼也可以看到真正加載的文件

然後我們繼續往下看源碼

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 因爲會加載多個 spring.factories 文件,那麼就有可能存在同名的,
            // removeDuplicates方法的作用是 移除同名的
            configurations = this.removeDuplicates(configurations);
            // 獲取我們配置的 exclude 信息
            // @SpringBootApplication(exclude = {RabbitAutoConfiguration.class})
            // 顯示的指定不要加載那個配置類
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            // filter的作用是 過濾掉咱們不需要使用的配置類。
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

先來看過濾的效果:

那麼我們需要考慮這個過濾到底是怎麼實現的呢?進入filter方法

我們可以看到有具體的匹配方法 match。裏面有個關鍵的屬性是 autoConfigurationMetadata,的本質是 加載的 META-INF/spring-autoconfigure-metadata.properties 的文件中的內容。我們以 RedisAutoConfiguration 爲例:

通過上面的配置文件,我們發現RedisAutoConfiguration 被注入到IoC中的條件是系統中要存在 org.springframework.data.redis.core.RedisOperations 這個class文件。首先系統中不存在 RedisOperations 這個class文件。

過濾後,我們發現 RedisAutoConfiguration 就不存在了。

但是當我們在系統中顯示的創建 RedisOperations Java類後,filter就不會過濾 RedisAutoConfiguration 配置文件了。

到這其實我們就已經給大家介紹完了SpringBoot的自動裝配原理。

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有你們的 『點贊和評論』,纔是我創造的動力。

  2. 關注公衆號 『 java爛豬皮 』,不定期分享原創知識。

  3. 同時可以期待後續文章ing🚀

  4. .關注後回覆【666】掃碼即可獲取學習資料包

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