http://www.cnblogs.com/supermaskv/p/12464830.html

Spring Boot 自動裝配流程

本文以 mybatis-spring-boot-starter 爲例簡單分析 Spring Boot 的自動裝配流程。

Spring Boot 發現自動配置類

這裏說的自動配置類指的是在 META-INF/spring.factories 文件中聲明的 XXXAutoConfiguration 類。

首先,我們從 @SpringBootApplication 註解的定義中,我們可以發現有一個叫做 @EnableAutoConfiguration 的註解,這也是 SpringBoot 實現自動裝配最關鍵的註解。

//@EnableAutoConfiguration 註解的定義@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 {};
}

@Target @Retention @Documented @Inherited 都是 jdk 提供的註解,有興趣可以去查查看,這裏就不做分析了。這裏主要分析 @AutoConfigurationPackage 和 @Import({AutoConfigurationImportSelector.class}) 究竟起到什麼作用。

//@AutoConfigurationPackage 註解的定義@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

在 @AutoConfigurationPackage 註解的定義中,我們又發現一個 @Import 註解。 @Import 註解是由 Spring 提供的,作用是將某個類實例化並加入到 Spring IoC 容器中。所以我們要想知道 @Import({Registrar.class}) 究竟做了什麼就需要了解 Registrar 這個類裏包含了哪些方法。

//Registrar 類的定義static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }    public Set<Object> determineImports(AnnotationMetadata metadata) {        return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
    }
}

Registrar 類裏一共有兩個方法,分別是 determineImports 和 registerBeanDefinitions 。

determineImports 方法在我的項目的啓動過程中並沒有觸發斷點,官方的文檔描述這個方法返回的是一組代表要導入項的對象。

registerBeanDefinitions 方法觸發斷點後發現

new AutoConfigurationPackages.PackageImport(metadata)).getPackageName() 方法返回的就是 @SpringBootApplication 註解所在的類的包名。

所以 @AutoConfigurationPackage 註解的作用應該是掃描與 @SpringBootApplication 標註的類同一包下的所有組件。

瞭解了 @AutoConfigurationPackage 註解後,我們回到 @EnableAutoConfiguration 的定義,還有一個 @Import({AutoConfigurationImportSelector.class}) 需要我們瞭解。 AutoConfigurationImportSelector 類定義的內容很多,我們着重瞭解其中一個重要的方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {    if (!this.isEnabled(annotationMetadata)) {        return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

這個方法中,除了 loadMetadata 獲取註解元數據之外,就是 getAutoConfigurationEntry 獲取自動配置條目。

//getAutoConfigurationEntry 的定義protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {    if (!this.isEnabled(annotationMetadata)) {        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        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.filter(configurations, autoConfigurationMetadata);        this.fireAutoConfigurationImportEvents(configurations, exclusions);        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

然後我們再進入到這個叫做 getCandidateConfigurations 的方法中,這個方法名告訴我們這個方法的作用是獲取候選配置。

//getCandidateConfigurations 的定義protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");    return configurations;
}

從這個方法中的 Assert.notEmpty() 中我們可以反推得出,Spring Boot 除了掃描自己 jar 包中 META-INF/spring.factories 之外,還會去找別的 jar 包中是否存在 META-INF/spring.factories 。這也爲第三方開發自己的 spring-boot-starter 提供了便利。

@Conditional 系列註解

在研究 mybatis-spring-boot-starter 之前,我們還需要了解一下 Spring 爲提供的強大的 @Conditional 系列註解。

iMBjEf7.png!web

@Conditional擴展註解作用(判斷當前條件是否滿足)
@ConditionalOnJava系統的java版本是否符合要求
@ConditionalOnBean容器中是否存在指定的Bean
@ConditionalOnMissingBean容器中不存在指定的類
@ConditionalOnExpression滿足SpEL表達式指定規範
@ConditionalOnClass在系統中有指定的對應的類
@ConditionalOnMissingClass在系統中沒有指定對應的類
@ConditionalOnSingleCandidate容器中是否指定一個單實例的Bean,或者找個是一個首選的Bean
@ConditionalOnProperty系統中指定的對應的屬性是否有對應的值
@ConditionalOnResource類路徑下是否存在指定的資源文件
@ConditionalOnWebApplication當前是Web環境
@ConditionalOnNotWebApplication當前不是Web環境
@ConditionalOnJndiJNDI存在指定項

表格中的系統指的是項目本身,而非操作系統。

mybatis-spring-boot-starter

在 mybatis-spring-boot-starter 中,我們可以看到內容很少,僅有一個 pom.xml 文件用於引入依賴,所以 mybatis-spring-boot-starter 並不直接起作用,而是利用其它依賴完成自動配置。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
    </dependency></dependencies>

我們可以發現 mybatis-spring-boot-starter 的依賴項中有一個叫做 mybatis-spring-boot-autoconfigure 的依賴項。這很有可能就是 mybatis 對自己完成自動裝配真正起作用的工具。

yYbABzI.png!web

果然在 mybatis-spring-boot-autoconfigure 的 META-INF 中我們找到了 spring.factories 文件。

# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

MybatisLanguageDriverAutoConfiguration 類是對各種語言的支持,如 Thymeleaf 、 FreeMarker 等。配置 Mybatis 核心組件的是 MybatisAutoConfiguration 類。

//MybatisAutoConfiguration 類定義@Configuration@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})@ConditionalOnSingleCandidate(DataSource.class)@EnableConfigurationProperties({MybatisProperties.class})@AutoConfigureAfter({DataSourceAutoConfiguration.class,
                     MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {    //....}

首先 MybatisAutoConfiguration 類是一個被 @Configuration 標記了的配置類。這不難理解, MybatisAutoConfiguration 類會爲 Mybatis 配置一些關鍵的 Bean 並加入到容器中去。

接着就是兩個 @Conditional 系列的註解,表示當項目中存在 SqlSessionFactory.class 與 SqlSessionFactoryBean.class 並且存在 DataSource.class 的單例實例或者首選實例時, MybatisAutoConfiguration 纔會被加入到容器中去。

@EnableConfigurationProperties({MybatisProperties.class}) 這個註解的作用是將配置文件( .propertyies 和 .yml)與 MybatisProperties 類關聯起來,也就是說這個註解能讓 Spring Boot 從配置文件中讀取數據。

@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) 這個註解的作用也非常明顯,就是要求 Spring Boot 在裝配 MybatisAutoConfiguration 之前先完成 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 的裝配。這樣可以保證 Mybatis 在裝配時,所有的依賴項都已經到位。

除了 MybatisAutoConfiguration 本身之外,類中也定義了一些按條件生成的 Bean,保證 Mybatis 能在各種條件下成功的自動裝配。

總結

  1. Spring Boot 在啓動時除了掃描與啓動類同一包下的組件之外,還會檢查各個 jar 包中是否存在 META-INF/spring.factories 文件,爲自動裝配做準備。

  2. 第三方的 spring-boot-starter 會通過將自己的自動裝配類寫到 META-INF/spring.factories 中讓 Spring Boot 加載到容器中,使自動裝配類能夠生效。

  3. 第三方的自動裝配類會通過利用 @Conditional 系列註釋保證自己能在各種環境中成功自動裝配。


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