前言 開心一刻 女兒: “媽媽,你這麼漂亮,當年怎麼嫁給了爸爸呢?” 媽媽: “當年你爸不是窮嘛!‘ 女兒: “窮你還嫁給他!” 媽媽: “那時候剛剛畢業參加工作,領導對我說,他是我的扶貧對象,我年輕理解錯了,就嫁給他了!” 女兒...... @Import註解應用 應用開發中,當我們的功能模塊比較 ...
前言
開心一刻
女兒: “媽媽,你這麼漂亮,當年怎麼嫁給了爸爸呢?”
媽媽: “當年你爸不是窮嘛!‘
女兒: “窮你還嫁給他!”
媽媽: “那時候剛剛畢業參加工作,領導對我說,他是我的扶貧對象,我年輕理解錯了,就嫁給他了!”
女兒......
@Import註解應用
應用開發中,當我們的功能模塊比較多時,往往會按模塊或類別對Spring的bean配置文件進行管理,使配置文件模塊化,更容易維護;spring3.0之前,對Spring XML bean文件進行拆分, 例如
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <import resource="config/user.xml"/> <import resource="config/role.xml"/> <import resource="config/permission.xml"/> </beans>
spring3.0及之後,引入了@Import註解,提供與Spring XML中的<import />元素等效的功能;spring4.2之前,@Import只支持導入配置類(@Configuration修飾的類、ImportSelector實現類和ImportBeanDefinitionRegistrar實現類),而spring4.2及之後不僅支持導入配置類,同時也支持導入常規的java類(如普通的User類)
示例地址:spring-boot-autoconfig,四種都有配置,不用down下來運行,看一眼具體如何配置即可
運行測試用例,結果如下
可以看到,Dog、Cat、Role、User、Permission的實例都已經註冊到了spring容器,也就是說上述講的@Import的4種方式都是能夠將實例註冊到spring容器的
@Import註解原理
@Import何以有如此強大的功能,背後肯定有某個團隊在運作,而這個團隊是誰了,就是spring;spring容器肯定在某個階段有對@Import進行了處理,至於spring是在什麼時候對@Import進行了怎樣的處理,我們來跟一跟源碼;ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor,那麼它會在spring啟動的refresh階段被應用,我們從refresh的invokeBeanFactoryPostProcessors方法開始
ConfigurationClassPostProcessor
註意此時spring容器中的bean定義與bean實例,數量非常少,大家可以留心觀察下
一路跟下來,我們來到processConfigBeanDefinitions方法,該方法會創建一個ConfigurationClassParser對象,該對象會分析所有@Configuration註解的配置類,產生一組ConfigurationClass對象,然後從這組ConfigurationClass對象中載入bean定義
ConfigurationClassParser
主要是parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<>(); // 通常情況下configCandidates中就一個BeanDefinitionHolder,關聯的是我們的啟動類 // 示例中是:com.lee.autoconfig.AutoConfigApplication for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 被@Configuration註解修飾的類會被解析為AnnotatedGenericBeanDefinition,AnnotatedGenericBeanDefinition實現類AnnotatedBeanDefinition介面 if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } // 處理延遲的ImportSelector,這裡本文的重點:自動配置的入口 processDeferredImportSelectors(); }View Code
從啟動類(示例中是com.lee.autoconfig.AutoConfigApplication)開始,遞歸解析配置類以及配置類的父級配置類;邊跟邊註意beanFactory中beanDefinitionMap的變化,ConfigurationClassParser對象有beanFactory的引用,屬性名叫registry;我們可以仔細看下doProcessConfigurationClass方法

/** * 通過從源類中讀取註解、成員和方法來構建一個完整的配置類:ConfigurationClass * 註意返回值,是父級類或null(null包含兩種情況,沒找到父級類或之前已經處理完成) */ @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 遞歸處理配置類內置的成員類 processMemberClasses(configClass, sourceClass); // 處理配置類上所有@PropertySource註解 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // 處理配置類上所有的@ComponentScan註解,包括@ComponentScans和ComponentScan Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // 立即掃描@ComponentScan修飾的配置類, // 通常是從啟動類所在的包(示例中是com.lee.autoconfig)開始掃描,掃描配置類(被@Configuration修飾的類) Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // 進一步檢查通過配置類掃描得到的bean定義集,併在需要時遞歸解析 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // 處理配置類上所有的@Import註解 // 包括@Import支持的4種類型:ImportSelector、ImportBeanDefinitionRegistrar、@Configuration和普通java類 // 普通java類會被按@Configuration方式處理 processImports(configClass, sourceClass, getImports(sourceClass), true); // 處理配置類上所有的@ImportResource註解,xml方式的bean就是其中之一 AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // 處理配置類中被@Bean修飾的方法 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // 處理預設的方法或介面 processInterfaces(configClass, sourceClass); // 處理父級類,如果有的話 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }View Code
上述代碼中寫了相關註釋,有興趣的同學可以更進一步的去跟,這裡我只跟下processImports方法,因為這個與自動配置息息相關
起始的ConfigurationClass包括:1、工程中所有我們自定義的被@Configuration修飾的類,示例中就只有AnimalConfig;2、應用的啟動類,示例中是:AutoConfigApplication。
我們自定義的ConfigurationClass一般不會包含多級父級ConfigurationClass,例如AnimalConfig,就沒有父級ConfigurationClass,解析就比較簡單,我們無需關註,但AutoConfigApplication就不一樣了,他往往會被多個註解修飾,而這些註解會牽扯出多個ConfigurationClass,需要遞歸處理所有的ConfigurationClass;上圖中,我們跟到了一個比較重要的類:AutoConfigurationImportSelector,實例化之後封裝成了DeferredImportSelectorHolder對象,存放到了ConfigurationClassParser的deferredImportSelectors屬性中
自動配置源碼解析
有人可能有這樣的疑問:哪來的AutoConfigurationImportSelector,它有什麼用? 客觀莫急,我們慢慢往下看
我們的應用啟動類被@SpringBootApplication,它是個組合註解,詳情如下
相信大家都看到@Import(AutoConfigurationImportSelector.class)了,ConfigurationClassParser就是從此解析到的AutoConfigurationImportSelector,至於AutoConfigurationImportSelector有什麼用,馬上揭曉;我們回到ConfigurationClassParser的parse方法,裡面還有個很重要的方法:processDeferredImportSelectors,值得我們詳細跟下
processDeferredImportSelectors
說的簡單點,從類路徑下的所有spring.facoties文件中讀取全部的自動配置類(spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的值),然後篩選出滿足條件的配置類,封裝成ConfigurationClass,存放到ConfigurationClassParser的configurationClasses屬性中
說的詳細點,分兩個方法進行說明
selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); // 從類路徑下的spring.factories文件中讀取所有配置類(org.springframework.boot.autoconfigure.EnableAutoConfigurationd的值) // 得到所有配置類的全路徑類名的集合 - 數組 // 此時得到的是類名,至於該類存不存在,還需要在下麵步驟中進行檢驗 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重重覆的 configurations = removeDuplicates(configurations); // 獲取需要排除的配置類,@SpringBootApplication exclude和excludeName的值 // 以及配置文件中spring.autoconfigure.exclude的值 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 驗證排除的配置類是否存在 - 類路徑下是否存在該類 checkExcludedClasses(configurations, exclusions); // 剔除需要排除的配置類 configurations.removeAll(exclusions); // 進行過濾 - 通過配置類的條件註解(@ConditionalOnClass、@ConditionalOnBean等)來判斷配置類是否符合條件 configurations = filter(configurations, autoConfigurationMetadata); // 觸發自動配置事件 - ConditionEvaluationReportAutoConfigurationImportListener fireAutoConfigurationImportEvents(configurations, exclusions); // 返回@Import方式 所有滿足條件的配置類 return StringUtils.toStringArray(configurations); }View Code
從類路徑下的所有spring.facoties文件中讀取org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有值,此時獲取的是全路徑類名的數組,然後進行篩選過濾,1、先去重處理,因為多個spring.factories中可能存在重覆的;2、然後剔除我們配置的需要排除的類,包括@SpringBootApplication註解的exclude、excludeName,以及配置文件中的spring.autoconfigure.exclude;3、條件過濾,過濾出滿足自己條件註解的配置類。最終獲取所有滿足條件的自動配置類,示例中有24個。
條件註解更詳細的信息請查看:spring-boot-2.0.3源碼篇 - @Configuration、Condition與@Conditional,讀取spring.facoties文件的詳細信息請查看:spring-boot-2.0.3啟動源碼篇一 - SpringApplication構造方法
processImports方法
這個方法在解析ConfigurationClassParser的parse方法的時候已經用到過了,只是沒有做說明,它其實就是用來處理配置類上的@Import註解的;上述selectImports方法解析出來的配置類,每個配置類都會經過processImports方法處理,遞歸處理@Import註解,就與遞歸處理我們的啟動類的@Import註解一樣,從而獲取所有的自動配置類;springboot的自動配置就是這樣實現的。
此時還只是獲取了滿足條件的自動配置類,配置類中的bean定義載入還沒有進行,我們回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,其中有如下代碼
// 各種方式的配置類的解析,包括springboot的自動配置 - @Import、AutoConfigurationImportSelector parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); // 將配置類中的bean定義載入到beanFactory
至此,springboot的自動配置源碼解析就完成了,有興趣的可以更近一步的深入
總結
1、各個方法之間的調用時序圖如下,結合這個時序圖看上面的內容,更好看懂
2、springboot自動配置底層依賴的是SpringFactoriesLoader和AutoConfigurationImportSelector;@EnableAutoConfiguration註解就像一個八爪魚,抓取所有滿足條件的配置類,然後讀取其中的bean定義到spring容器,@EnableAutoConfiguration得以生效的關鍵組件關係圖如下