Spring Boot示例分析講解自動化裝配機制核心注解
1. 自動化裝配介紹
Spring Boot針對mvc做了大量封裝,簡化開發(fā)者的使用,內(nèi)部是如何管理資源配置,Bean配置,環(huán)境變量配置以及啟動配置等? 實質(zhì)是SpringBoot做了大量的注解封裝,比如@SpringBootApplication, 同時采用Spring 4框架的新特性@Conditional基于條件的Bean創(chuàng)建管理機制來實現(xiàn);
實際的工作場景中是復(fù)雜多樣的, 有些項目需要不同的組件, 比如REDIS、MONGODB作緩存; RABBITMQ、KAFKA作消息隊列; 有些項目運行環(huán)境不同, 比如JDK7、JDK8不同版本,面對眾多復(fù)雜的需求, 又要做到最大化支持, Spring Boot是如何管理實現(xiàn)的, 這就依賴Conditional功能,基于條件的自動化配置。
2. Spring Boot 自動化配置UML圖解
SpringBootApplication是我們所常用熟知的注解, 它是一個組合注解, 依賴多個注解,共同實現(xiàn)Spring Boot應(yīng)用功能, 以下為所有依賴的UML圖解,我們圍繞這些注解深入研究,看下具體的實現(xiàn)。
3. Spring Boot 自動化配置核心注解分析
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) }) @ConfigurationPropertiesScan public @interface SpringBootApplication { /** * 需要排除的自動化配置, 根據(jù)類名進行排除, 比如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等 */ @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; /** * 需要排除的自動化配置, 根據(jù)名稱進行排除 */ @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; /** * 指定需要掃描的包路徑,參數(shù)填寫包名 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; /** * 指定需要掃描的包路徑 */ @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; /** * Bean方法的動態(tài)代理配置, 如果沒有采用工廠方法, 可以標記為false, 采用cglib代理。 */ @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
3.1 @Inherited
java.lang.annotation.@Inherited 注解,從包名可以看出為JDK自帶注解, 作用是讓子類能夠繼承父類中引用Inherited的注解, 但需注意的是, 該注解作用范圍只在類聲明中有效; 如果是接口與接口的繼承, 類與接口的繼承, 是不會生效。
3.2 @SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration(proxyBeanMethods = false) public @interface SpringBootConfiguration { /** * Bean方法的動態(tài)代理配置 */ @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true; }
這是配置型處理注解, 可以看到內(nèi)部源碼引用了@Configuration注解,
自身沒有太多的實現(xiàn), 那為什么還需要再包裝?官方給出的解釋是對Spring的@Configuration的擴展,
用于實現(xiàn)SpringBoot的自動化配置。proxyBeanMethods屬性默認為true, 作用是對bean的方法是否開啟代理方式調(diào)用, 默認為true, 如果沒有采用工廠方法,可以設(shè)為false, 通過cglib作動態(tài)代理。
3.3 @EnableAutoConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { // 設(shè)置注解支持重載的標識 String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * 排除自動化配置的組件, 如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等 */ Class<?>[] exclude() default {}; /** * 排除自動化配置的組件, 根據(jù)名稱設(shè)置 */ String[] excludeName() default {}; }
用于管理開啟Spring Boot的各種自動化配置注解, 如datasource, mongodb, redis等,也是spring-boot-autoconfigure工程的核心注解。
AutoConfigurationPackage
它的主要作用是掃描主程序同級及下級的包路徑所有Bean與組件注冊到Spring Ioc容器中。
Import
它可以把沒有聲明配置的類注冊到Spring Ioc容器中管理引用。 導入的AutoConfigurationImportSelector類實現(xiàn)BeanClassLoaderAware、ResourceLoaderAware、EnvironmentAware等接口, 管理類裝載器, 資源裝載器及環(huán)境配置等, 是一個負責處理自動化配置導入的選擇管理器。在下面【@AutoConfigurationImportSelector剖析】進行詳解。
3.4 @ComponentScan
這是我們在Spring下面常用的一個注解,它可以掃描Spring定義的注解, 如@Componment, @Service等, 常用的屬性有basePackages掃描路徑,includeFilters包含路徑過濾器, excludeFilters排除路徑過濾器,lazyInit是否懶加載等,能夠非常靈活的掃描管理需要注冊組件。
3.5 @ConfigurationPropertiesScan
作用是掃描指定包及子包路徑下面的ConfigurationProperties注解,管理工程配置屬性信息。主要屬性為basePackages掃描路徑, 支持多個路徑,數(shù)組形式;basePackageClasses屬性也可以具體到包下面的類,
支持多個配置。
3.6 @AutoConfigurationImportSelector
AutoConfigurationImportSelector 實現(xiàn) DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口, 為自動化配置的核心處理類, 主要負責自動化配置規(guī)則的一系列處理邏輯:
/** * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration * auto-configuration}. This class can also be subclassed if a custom variant of * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed. * * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll * @author Madhura Bhave * @since 1.3.0 * @see EnableAutoConfiguration */ public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { ... } }
講解幾個技術(shù)點:
getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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; }
該方法是獲取所有Spring Boot聲明定義的自動化配置類。
看下具體有哪些信息:
這些實際是配置在Spring-boot-autoconfigure工程下的META-INF/spring.factories文件中:
看到這里, 我們應(yīng)該可以明白,為什么AOP,RABBIT,DATASOURCE, REIDS等組件SPRING BOOT都能幫我們快速配置實現(xiàn),其實它內(nèi)部遵循SPI機制, 已經(jīng)把自動化配置做好了封裝。
AutoConfigurationGroup類
它是AutoConfigurationImportSelector的內(nèi)部類,實現(xiàn)了DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware接口,是一個重要的核心類。主要作用是負責自動化配置條目信息的記錄, 排序,元數(shù)據(jù)處理等。它通過getImportGroup方法獲取返回,該方法實現(xiàn)DeferredImportSelector的接口。
private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware { // 記錄注解的元數(shù)據(jù) private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>(); // 記錄自動化配置條目,放入集合 private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>(); // 設(shè)置bean的類加載器 private ClassLoader beanClassLoader; // 設(shè)置bean工廠信息 private BeanFactory beanFactory; // 設(shè)置資源加載器信息 private ResourceLoader resourceLoader; // 設(shè)置自動化配置的元數(shù)據(jù)記錄 private AutoConfigurationMetadata autoConfigurationMetadata; ... }
屬性主要定義了一些自動化配置類目信息、BEAN工廠、類和資源加載器信息。entries條目有22條, 具體內(nèi)容如下:
里面是主要的自動化配置類的元數(shù)據(jù)信息,autoConfigurationEntries屬性就是具體的自動化配置條目。這些主要自動化類配置是Spring boot幫助我們實現(xiàn)mvc的核心功能,如請求分發(fā),文件上傳,參數(shù)驗證,編碼轉(zhuǎn)換等功能。還有一部分是定制條件自動化配置類,
autoConfigurationMetadata元數(shù)據(jù)內(nèi)容較多, 包含各種組件, 根據(jù)環(huán)境配置和版本不同, 這里可以看到共有705個:
由于Spring Boot支持眾多插件,功能豐富, 數(shù)量較多; 這里存在些疑問, 這里面的元數(shù)據(jù)和上面的entries條目都是AutoConfiguration自動化配置類, 那有什么區(qū)別? 其實這里面的, 都是基于條件的自動化配置。
我們就拿KafkaAutoConfiguration來看:
可以看到注解ConditionalOnClass,意思是KafkaAutoConfiguration生效的前提是基于KafkaTemplate類的初始化成功,這就是定制條件,也就是基于條件的自動化配置類,雖然有七百多個,但其實是根據(jù)工程實際用到的組件,才會觸發(fā)加載對應(yīng)的配置。 有關(guān)Conditional基于條件的自動化配置實現(xiàn)原理, 在下面我們再作深入研究。
繼續(xù)看AutoConfigurationImportSelector內(nèi)部類的selectImports方法:
@Override public Iterable<Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } // 將所有自動化條目根據(jù)配置的Exclusion條件作過濾, 并轉(zhuǎn)換為SET集合 Set<String> allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); // SET集合, 記錄所有需要處理的自動化配置 Set<String> processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); // 兩個SET, 做交集過濾, 排除不需要的配置 processedConfigurations.removeAll(allExclusions); // 最后進行排序處理 return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); }
該方法是針對autoConfigurationEntries自動化配置條目做過濾,根據(jù)指定的排除規(guī)則處理;再根據(jù)設(shè)置的啟動的優(yōu)先級做排序整理。從代碼中可以看到,先獲取所有的allExclusions排除配置信息,再獲取所有需要處理的processedConfigurations配置信息,然后做過濾處理,最后再調(diào)用sortAutoConfigurations方法,根據(jù)order順序做排序整理。
AutoConfigurationImportSelector內(nèi)部類的process方法:
@Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); // 獲取自動化配置條目 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); // 記錄獲取的條目 this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { // 放入成員變量entries中 this.entries.putIfAbsent(importClassName, annotationMetadata); } }
該方法是掃描獲取autoConfigurationEntries自動化配置條目信息。
annotationMetadata參數(shù):
為注解元數(shù)據(jù),有也就是被@SpringBootApplication修飾的類信息,在這里就是我們的啟動入口類信息。
deferredImportSelector參數(shù):
通過@EnableAutoConfiguration注解定義的 @Import 的類,也就是AutoConfigurationImportSelector對象。根據(jù)配置,會加載指定的beanFactory、classLoader、resourceLoader和environment對象。
AutoConfigurationImportSelector內(nèi)部類的getAutoConfigurationEntry方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { // 1、判斷是否開對應(yīng)注解 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 2、獲取注解定義的屬性 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 3、獲取符合規(guī)則的Spring Boot 內(nèi)置的自動化配置類, 并做去重處理 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); // 4、做排除規(guī)則匹配, 過濾處理 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); // 5、觸發(fā)自動導入處理完成事件 fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
該方法主要作用是獲取Spring Boot 內(nèi)置的自動化條目, 例AopAutoConfiguration等,該方法會調(diào)用上面講解的getCandidateConfigurations方法。 主要步驟邏輯如下:
- 判斷是否開啟元注解掃描, 對應(yīng)屬性為spring.boot.enableautoconfiguration,默認情況下, 是開啟自動配置。
- 獲取定義的注解屬性, 跟蹤內(nèi)部源碼, 里面會返回exclude和excludeName等屬性。
- 獲取符合規(guī)則的Spring Boot 內(nèi)置的自動化配置, 并做去重處理,也就是我們上面講解的getCandidateConfigurations方法, 從中我們就可以理解其中的關(guān)聯(lián)關(guān)系。
- 做排除規(guī)則檢查與過濾處理, 根據(jù)上面第2個步驟獲取的exclude等屬性以及配置屬性spring.autoconfigure.exclude做過濾處理。
- 觸發(fā)自動導入完成事件, 該方法內(nèi)部邏輯正常處理完成才會觸發(fā),會調(diào)用AutoConfigurationImportListener監(jiān)聽器做通知處理。
3.7 @AutoConfigurationPackages
AutoConfigurationPackages是EnableAutoConfiguration上的另一個核心注解類, 官方解釋為:
Indicates that the package containing the annotated class should be registered
意思是包含該注解的類,所在包下面的class, 都會注冊到Spring Ioc容器中。對應(yīng)源碼:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage { ... }
Import注解, 導入AutoConfigurationPackages抽象類下面的內(nèi)部靜態(tài)類Registrar,研究Registrar實現(xiàn)原理:
Registrar實現(xiàn) ImportBeanDefinitionRegistrar、DeterminableImports 接口,它負責存儲從@AutoConfigurationPackage注解掃描到的信息。 源碼如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { // 注冊BEAN的定義信息 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); } // 決定是否導入注解中的配置內(nèi)容 @Override public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImport(metadata)); } }
這里面主要涉及到PackageImport類, 它是AutoConfigurationPackages的內(nèi)部私有靜態(tài)類,主要是記錄導入的 報名信息, 源碼如下:
/** * Wrapper for a package import. */ private static final class PackageImport { private final String packageName; // 構(gòu)造方法, 記錄注解內(nèi)容 PackageImport(AnnotationMetadata metadata) { this.packageName = ClassUtils.getPackageName(metadata.getClassName()); } // 獲取指定包名稱 public String getPackageName() { return this.packageName; } // 重載父類比較邏輯, 根據(jù)包名判斷 @Override public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; } return this.packageName.equals(((PackageImport) obj).packageName); } // 重載hash標識, 以包名的HASH值為準 @Override public int hashCode() { return this.packageName.hashCode(); } // 重載toString, 打印內(nèi)容 @Override public String toString() { return "Package Import " + this.packageName; } }
內(nèi)部斷點跟蹤的話, 可以看到它記錄的是我們啟動類所在的包名。這也就是為什么不需要指定掃描包路徑, 也會加載啟動類所在包下面的JavaConfig配置信息。
回到上面Registrar的registerBeanDefinitions方法, 內(nèi)部調(diào)用的是register方法:
它是處理記錄AutoConfigurationPackages掃描包信息,源碼如下:
public static void register(BeanDefinitionRegistry registry, String... packageNames) { // 判斷是否包含BEAN定義信息, 如果包含, 更新packageNames信息 if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } // 如果registry中不包含BEAN定義, 重新構(gòu)造GenericBeanDefinition對象, 記錄相關(guān)信息 else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN, beanDefinition); } }
先判斷AutoConfigurationPackages注解, 記錄對應(yīng)的掃描包信息;如果不存在,則自行創(chuàng)建基于BasePackages的BEAN定義信息, 并進行注冊。再看下addBasePackages方法:
private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) { // 獲取已經(jīng)存在的Bean定義信息 String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue(); // 創(chuàng)建合并集合, 過濾重復(fù)的Bean定義 Set<String> merged = new LinkedHashSet<>(); // 根據(jù)Set特性, 自動合并去重 merged.addAll(Arrays.asList(existing)); merged.addAll(Arrays.asList(packageNames)); return StringUtils.toStringArray(merged); }
獲取已經(jīng)存在的定義信息,再和packageNames合并, 過濾重復(fù)的掃描包。
自動化配置到此就不再對其他代碼進行深入跟蹤分析,Spring Boot整個框架代碼還是較多, 大家可以按這種思路, 逐個層級去剖析,深入挖掘更多技術(shù)點。
4. 總結(jié)
我們研究了Spring Boot的自動化配置原理,逐層研究剖析,從@SpringBootApplication啟動注解開始,到下面的@SpringBootConfiguration, @ConfigurationPropertiesScan, @ComponentScan以及核心@EnableAutoConfiguration。我們對@EnableAutoConfiguration下面的@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)兩個重要注解作了深入研究,從中可以看到Spring Boot針對自動化配置, 是分為兩部分, 一部分是核心注解,來支撐服務(wù)的正常運行; 另一部分是非核心的各種自動化組件注解,做了大量封裝,便于我們集成使用。
到此這篇關(guān)于Spring Boot示例分析講解自動化裝配機制核心注解的文章就介紹到這了,更多相關(guān)Spring Boot自動化裝配機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java8如何用Stream查List對象某屬性是否有重復(fù)
這篇文章主要介紹了java8如何用Stream查List對象某屬性是否有重復(fù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09JPA如何將查詢結(jié)果轉(zhuǎn)換為DTO對象
這篇文章主要介紹了JPA如何將查詢結(jié)果轉(zhuǎn)換為DTO對象,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Springmvc基于fastjson實現(xiàn)導包及配置文件
這篇文章主要介紹了Springmvc基于fastjson實現(xiàn)導包及配置文件,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10java中hashCode方法與equals方法的用法總結(jié)
總的來說,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內(nèi)的元素是有序的,元素可以重復(fù);后者元素無序,但元素不可重復(fù)2013-10-10springboot基于IDEA環(huán)境熱加載與熱部署教程
這篇文章主要為大家介紹了springboot在IDEA環(huán)境下的熱加載與熱部署教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03SpringBoot集成mybatis連接oracle的圖文教程
這篇文章主要介紹了Spring Boot集成mybatis連接oracle的圖文教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02