Spring Boot 自動裝配流程


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 系列注解。

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

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

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 對自己完成自動裝配真正起作用的工具。

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

# Auto Configure
org.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 系列注釋保證自己能在各種環境中成功自動裝配。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM