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 能在各種條件下成功的自動裝配。
總結
- Spring Boot 在啟動時除了掃描與啟動類同一包下的組件之外,還會檢查各個 jar 包中是否存在
META-INF/spring.factories文件,為自動裝配做准備。 - 第三方的
spring-boot-starter會通過將自己的自動裝配類寫到META-INF/spring.factories中讓 Spring Boot 加載到容器中,使自動裝配類能夠生效。 - 第三方的自動裝配類會通過利用
@Conditional系列注釋保證自己能在各種環境中成功自動裝配。
