什么是 SpringBoot 自動裝配?
我們現在提到自動裝配的時候,一般會和 Spring Boot 聯系在一起。但是,實際上 Spring Framework 早就實現了這個功能。Spring Boot 只是在其基礎上,通過 SPI 的方式,做了進一步優化。
SpringBoot 定義了一套接口規范,這套規范規定:SpringBoot 在啟動時會掃描外部引用 jar
包中的META-INF/spring.factories文件,將文件中配置的類型信息加載到 Spring 容器(此處涉及到 JVM
類加載機制與 Spring 的容器知識),并執行類中定義的各種操作。對于外部 jar 來說,只需要按照 SpringBoot
定義的標準,就能將自己的功能裝置進 SpringBoot。
沒有 Spring Boot 的情況下,如果我們需要引入第三方依賴,需要手動配置,非常麻煩。但是,Spring Boot 中,我們直接引入一個 starter 即可。比如你想要在項目中使用 redis 的話,直接在項目中引入對應的 starter 即可。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入 starter 之后,我們通過少量注解和一些簡單的配置就能使用第三方組件提供的功能了。在我看來,自動裝配可以簡單理解為:通過注解或者一些簡單的配置就能在 Spring Boot 的幫助下實現某塊功能。
SpringBoot 是如何實現自動裝配的?
我們先看一下 SpringBoot 的核心注解 SpringBootApplication 。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
<1.>@SpringBootConfiguration
<2.>@ComponentScan
<3.>@EnableAutoConfiguration
public @interface SpringBootApplication {}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //實際上它也是一個配置類
public @interface SpringBootConfiguration {
}
大概可以把 @SpringBootApplication看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。根據 SpringBoot 官網,這三個注解的作用分別是:
- @EnableAutoConfiguration:啟用 SpringBoot 的自動配置機制
- @Configuration:允許在上下文中注冊額外的 bean 或導入其他配置類
- @ComponentScan:掃描被@Component (@Service,@Controller)注解的 bean,注解默認會掃描啟動類所在的包下所有的類 ,可以自定義不掃描某些 bean。如下圖所示,容器中將排除TypeExcludeFilter和AutoConfigurationExcludeFilter。
@EnableAutoConfiguration 是實現自動裝配的重要注解,我們以這個注解入手。
@EnableAutoConfiguration:實現自動裝配的核心注解
EnableAutoConfiguration 只是一個簡單地注解,自動裝配核心功能的實現實際是通過 AutoConfigurationImportSelector類。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:將main包下的所有組件注冊到容器中
@Import({AutoConfigurationImportSelector.class}) //加載自動裝配類 xxxAutoconfiguration
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}
我們現在重點分析下AutoConfigurationImportSelector 類到底做了什么?
AutoConfigurationImportSelector:加載自動裝配類
AutoConfigurationImportSelector類的繼承體系如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}public interface DeferredImportSelector extends ImportSelector {}public interface ImportSelector {String[] selectImports(AnnotationMetadata var1);
}
可以看出,AutoConfigurationImportSelector 類實現了 ImportSelector接口,也就實現了這個接口中的 selectImports方法,該方法主要用于獲取所有符合條件的類的全限定類名,這些類需要被加載到 IoC 容器中。
private static final String[] NO_IMPORTS = new String[0];public String[] selectImports(AnnotationMetadata annotationMetadata) {// <1>.判斷自動裝配開關是否打開if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {//<2>.獲取所有需要裝配的beanAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}
這里我們需要重點關注一下getAutoConfigurationEntry()
方法,這個方法主要負責加載自動配置類的。
該方法調用鏈如下:
現在我們結合getAutoConfigurationEntry()的源碼來詳細分析一下:
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {//<1>.if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {//<2>.AnnotationAttributes attributes = this.getAttributes(annotationMetadata);//<3>.List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//<4>.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);}}
第 1 步:
判斷自動裝配開關是否打開。默認spring.boot.enableautoconfiguration=true
,可在 application.properties 或 application.yml 中設置
第 2 步:
用于獲取EnableAutoConfiguration注解中的 exclude 和 excludeName。
第 3 步
獲取需要自動裝配的所有配置類,讀取META-INF/spring.factories
spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
從下圖可以看到這個文件的配置內容都被我們讀取到了。XXXAutoConfiguration的作用就是按需加載組件。
不光是這個依賴下的META-INF/spring.factories被讀取到,所有 Spring Boot Starter 下的META-INF/spring.factories都會被讀取到。
所以,你可以清楚滴看到, druid 數據庫連接池的 Spring Boot Starter 就創建了META-INF/spring.factories文件。
如果,我們自己要創建一個 Spring Boot Starter,這一步是必不可少的。
第 4 步:
到這里可能面試官會問你:“spring.factories中這么多配置,每次啟動都要全部加載么?”。很明顯,這是不現實的。我們 debug 到后面你會發現,configurations 的值變小了。
因為,這一步有經歷了一遍篩選,@ConditionalOnXXX 中的所有條件都滿足,該類才會生效。
@Configuration
// 檢查相關的類:RabbitTemplate 和 Channel是否存在
// 存在才會加載
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
有興趣的童鞋可以詳細了解下 Spring Boot 提供的條件注解
- @ConditionalOnBean:當容器里有指定 Bean 的條件下@ConditionalOnMissingBean:當容器里沒有指定 Bean 的情況下@ConditionalOnSingleCandidate:當指定 Bean 在容器中只有一個,或者雖然有多個但是指定首選 Bean@ConditionalOnClass:當類路徑下有指定類的條件下@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下@ConditionalOnProperty:指定的屬性是否有指定的值@ConditionalOnResource:類路徑是否有指定的值@ConditionalOnExpression:基于 SpEL 表達式作為判斷條件@ConditionalOnJava:基于 Java 版本作為判斷條件@ConditionalOnJndi:在 JNDI 存在的條件下差在指定的位置@ConditionalOnNotWebApplication:當前項目不是 Web 項目的條件下@ConditionalOnWebApplication:當前項目是 Web 項 目的條件下#
如何實現一個 Starter
現在就來擼一個 starter,實現自定義線程池
第一步,創建threadpool-spring-boot-starter工程
第二步,引入 Spring Boot 相關依賴
第三步,創建ThreadPoolAutoConfiguration
第四步,在threadpool-spring-boot-starter工程的 resources 包下創建META-INF/spring.factories文件
最后新建工程引入threadpool-spring-boot-starter
測試通過!!!
總結
Spring Boot 通過@EnableAutoConfiguration
開啟自動裝配,通過 SpringFactoriesLoader
最終加載META-INF/spring.factories
中的自動配置類實現自動裝配,自動配置類其實就是通過@Conditional
按需加載的配置類,想要其生效必須引入spring-boot-starter-xxx
包實現起步依賴