開心一刻 html
女兒: 「媽媽,你這麼漂亮,當年怎麼嫁給了爸爸呢?」
媽媽: 「當年你爸不是窮嘛!‘
女兒: 「窮你還嫁給他!」
媽媽: 「那時候剛剛畢業參加工做,領導對我說,他是個人扶貧對象,我年輕理解錯了,就嫁給他了!」
女兒......java
應用開發中,當咱們的功能模塊比較多時,每每會按模塊或類別對Spring的bean配置文件進行管理,使配置文件模塊化,更容易維護;spring3.0以前,對Spring XML bean文件進行拆分, 例如git
<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
示例地址:spring-boot-autoconfig,四種都有配置,不用down下來運行,看一眼具體如何配置便可數組
運行測試用例,結果以下springboot
能夠看到,Dog、Cat、Role、User、Permission的實例都已經註冊到了spring容器,也就是說上述講的@Import的4種方式都是可以將實例註冊到spring容器的ide
@Import何以有如此強大的功能,背後確定有某個團隊在運做,而這個團隊是誰了,就是spring;spring容器確定在某個階段有對@Import進行了處理,至於spring是在何時對@Import進行了怎樣的處理,咱們來跟一跟源碼;ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor,那麼它會在spring啓動的refresh階段被應用,咱們從refresh的invokeBeanFactoryPostProcessors方法開始模塊化
注意此時spring容器中的bean定義與bean實例,數量很是少,你們能夠留心觀察下spring-boot
一路跟下來,咱們來到processConfigBeanDefinitions方法,該方法會建立一個ConfigurationClassParser對象,該對象會分析全部@Configuration註解的配置類,產生一組ConfigurationClass對象,而後從這組ConfigurationClass對象中加載bean定義測試
主要是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(); }
從啓動類(示例中是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; }
上述代碼中寫了相關注釋,有興趣的同窗能夠更進一步的去跟,這裏我只跟下processImports方法,由於這個與自動配置息息相關
起始的ConfigurationClass包括:一、工程中全部咱們自定義的被@Configuration修飾的類,示例中就只有AnimalConfig;二、應用的啓動類,示例中是:AutoConfigApplication。
咱們自定義的ConfigurationClass通常不會包含多級父級ConfigurationClass,例如AnimalConfig,就沒有父級ConfigurationClass,解析就比較簡單,咱們無需關注,但AutoConfigApplication就不同了,他每每會被多個註解修飾,而這些註解會牽扯出多個ConfigurationClass,須要遞歸處理全部的ConfigurationClass;上圖中,咱們跟到了一個比較重要的類:AutoConfigurationImportSelector,實例化以後封裝成了DeferredImportSelectorHolder對象,存放到了ConfigurationClassParser的deferredImportSelectors屬性中
有人可能有這樣的疑問:哪來的AutoConfigurationImportSelector,它有什麼用? 客觀莫急,咱們慢慢往下看
咱們的應用啓動類被@SpringBootApplication,它是個組合註解,詳情以下
相信你們都看到@Import(AutoConfigurationImportSelector.class)了,ConfigurationClassParser就是今後解析到的AutoConfigurationImportSelector,至於AutoConfigurationImportSelector有什麼用,立刻揭曉;咱們回到ConfigurationClassParser的parse方法,裏面還有個很重要的方法: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); }
從類路徑下的全部spring.facoties文件中讀取org.springframework.boot.autoconfigure.EnableAutoConfiguration的全部值,此時獲取的是全路徑類名的數組,而後進行篩選過濾,一、先去重處理,由於多個spring.factories中可能存在重複的;二、而後剔除咱們配置的須要排除的類,包括@SpringBootApplication註解的exclude、excludeName,以及配置文件中的spring.autoconfigure.exclude;三、條件過濾,過濾出知足本身條件註解的配置類。最終獲取全部知足條件的自動配置類,示例中有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的自動配置源碼解析就完成了,有興趣的能夠更近一步的深刻
一、各個方法之間的調用時序圖以下,結合這個時序圖看上面的內容,更好看懂
二、springboot自動配置底層依賴的是SpringFactoriesLoader和AutoConfigurationImportSelector;@EnableAutoConfiguration註解就像一個八爪魚,抓取全部知足條件的配置類,而後讀取其中的bean定義到spring容器,@EnableAutoConfiguration得以生效的關鍵組件關係圖以下