自定義過starter的同學應該都知道,自動配置類需要用 EnableAutoConfiguration 注解修飾,並且需要將自動配置類配置在spring.factories中。但自動配置類是如何被SpringBoot加載的呢?
網上有些文章已經講述了 EnableAutoConfiguration 注解類由 @Import(AutoConfigurationImportSelector.class) 修飾,已經SpringBoot是從spring.factories文件中讀取自動配置類的。我這里結合SpringBoot的啟動流程,記述一下自動配置類的加載過程。
首先,從SpringBoot項目的啟動類的SpringBootApplication.run(#,#)方法查看源碼,可以看到最終執行的是以下代碼:
1 /** 2 * Run the Spring application, creating and refreshing a new 3 * {@link ApplicationContext}. 4 * @param args the application arguments (usually passed from a Java main method) 5 * @return a running {@link ApplicationContext} 6 */ 7 public ConfigurableApplicationContext run(String... args) { 8 StopWatch stopWatch = new StopWatch(); 9 stopWatch.start(); 10 ConfigurableApplicationContext context = null; 11 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 12 configureHeadlessProperty(); 13 SpringApplicationRunListeners listeners = getRunListeners(args); 14 listeners.starting(); 15 try { 16 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 17 args); 18 ConfigurableEnvironment environment = prepareEnvironment(listeners, 19 applicationArguments); 20 configureIgnoreBeanInfo(environment); 21 Banner printedBanner = printBanner(environment); 22 context = createApplicationContext(); 23 exceptionReporters = getSpringFactoriesInstances( 24 SpringBootExceptionReporter.class, 25 new Class[] { ConfigurableApplicationContext.class }, context); 26 prepareContext(context, environment, listeners, applicationArguments, 27 printedBanner); 28 refreshContext(context); 29 afterRefresh(context, applicationArguments); 30 stopWatch.stop(); 31 if (this.logStartupInfo) { 32 new StartupInfoLogger(this.mainApplicationClass) 33 .logStarted(getApplicationLog(), stopWatch); 34 } 35 listeners.started(context); 36 callRunners(context, applicationArguments); 37 } 38 catch (Throwable ex) { 39 handleRunFailure(context, ex, exceptionReporters, listeners); 40 throw new IllegalStateException(ex); 41 } 42 43 try { 44 listeners.running(context); 45 } 46 catch (Throwable ex) { 47 handleRunFailure(context, ex, exceptionReporters, null); 48 throw new IllegalStateException(ex); 49 } 50 return context; 51 }
可以看到SpringBoot啟動時的主要內容是創建Environment、ApplicationContext以及對應事件的發布。代碼詳情,這里不涉及。我們這關注SpringBoot是在哪一步加載自動配置類的。而這一步就是第28行的 refreshContext(context); 追蹤 refreshContext(context); 可以發現,實際執行的代碼是 AbstractApplicationContext 的 refresh 方法, 代碼如下:
1 @Override 2 public void refresh() throws BeansException, IllegalStateException { 3 synchronized (this.startupShutdownMonitor) { 4 // Prepare this context for refreshing. 5 prepareRefresh(); 6 7 // Tell the subclass to refresh the internal bean factory. 8 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 9 10 // Prepare the bean factory for use in this context. 11 prepareBeanFactory(beanFactory); 12 13 try { 14 // Allows post-processing of the bean factory in context subclasses. 15 postProcessBeanFactory(beanFactory); 16 17 // Invoke factory processors registered as beans in the context. 18 invokeBeanFactoryPostProcessors(beanFactory); 19 20 // Register bean processors that intercept bean creation. 21 registerBeanPostProcessors(beanFactory); 22 23 // Initialize message source for this context. 24 initMessageSource(); 25 26 // Initialize event multicaster for this context. 27 initApplicationEventMulticaster(); 28 29 // Initialize other special beans in specific context subclasses. 30 onRefresh(); 31 32 // Check for listener beans and register them. 33 registerListeners(); 34 35 // Instantiate all remaining (non-lazy-init) singletons. 36 finishBeanFactoryInitialization(beanFactory); 37 38 // Last step: publish corresponding event. 39 finishRefresh(); 40 } 41 42 catch (BeansException ex) { 43 if (logger.isWarnEnabled()) { 44 logger.warn("Exception encountered during context initialization - " + 45 "cancelling refresh attempt: " + ex); 46 } 47 48 // Destroy already created singletons to avoid dangling resources. 49 destroyBeans(); 50 51 // Reset 'active' flag. 52 cancelRefresh(ex); 53 54 // Propagate exception to caller. 55 throw ex; 56 }
關注第18行的 invokeBeanFactoryPostProcessors(beanFactory); ,這一步會執行實現了 BeanFactoryPostProcessor 接口的類;這個接口是Spring初始化bean時對外暴露的入口,它可以修改bean工廠內所有的beandefinition(未實例化)數據,可以隨心所欲的修改屬性。追蹤代碼,可以看到實際執行的是 PostProcessorRegistrationDelegate 的 invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors) 方法。這個方法比較復雜,分為了好幾部分去處理,截取其中我們關心的部分即可(其實還包含了優先級、屬性等類似處理過程)。
1 // Do not initialize FactoryBeans here: We need to leave all regular beans 2 // uninitialized to let the bean factory post-processors apply to them! 3 // Separate between BeanDefinitionRegistryPostProcessors that implement 4 // PriorityOrdered, Ordered, and the rest. 5 List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>(); 6 7 // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. 8 String[] postProcessorNames = 9 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); 10 for (String ppName : postProcessorNames) { 11 if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { 12 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); 13 processedBeans.add(ppName); 14 } 15 } 16 sortPostProcessors(currentRegistryProcessors, beanFactory); 17 registryProcessors.addAll(currentRegistryProcessors); 18 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); 19 currentRegistryProcessors.clear();
第18行的代碼是我們要關注的,在這一步時,currentRegistryProcessors只有一個元素: ConfigurationClassPostProcessor ; 下一步執行的具體代碼就是 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法;然后執行到 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 方法,這個方法內容比較多,也比較復雜,這里也只貼出我們需要關注的幾行代碼:
1 // Parse each @Configuration class 2 ConfigurationClassParser parser = new ConfigurationClassParser( 3 this.metadataReaderFactory, this.problemReporter, this.environment, 4 this.resourceLoader, this.componentScanBeanNameGenerator, registry); 5 6 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); 7 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 8 do { 9 parser.parse(candidates); 10 parser.validate();
這里new了一個 ConfigurationClassParser 實例,並且執行了它的 parse(Set<BeanDefinitionHolder> configCandidates) 方法, 注意這里傳入的參數雖然是一個Set,但實際只有我們的啟動類一個元素。這個方法的代碼如下:
1 public void parse(Set<BeanDefinitionHolder> configCandidates) { 2 for (BeanDefinitionHolder holder : configCandidates) { 3 BeanDefinition bd = holder.getBeanDefinition(); 4 try { 5 if (bd instanceof AnnotatedBeanDefinition) { 6 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); 7 } 8 else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { 9 parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); 10 } 11 else { 12 parse(bd.getBeanClassName(), holder.getBeanName()); 13 } 14 } 15 catch (BeanDefinitionStoreException ex) { 16 throw ex; 17 } 18 catch (Throwable ex) { 19 throw new BeanDefinitionStoreException( 20 "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); 21 } 22 } 23 24 this.deferredImportSelectorHandler.process(); 25 }
在這個方法里,實際執行了第6行的代碼。這里不是我們的目標,先不關注;注意第24行的代碼,好像和我們看到了文章開頭提到的 AutoConfigurationImportSelector 有點關聯了,沒錯,這里就是實際加載自動配置類的地方。在這個process方法里,可以看到這里的importSelector元素就是 AutoConfigurationImportSelector ,到了這里我們有理由推測,SpringBoot是查找所有由 AutoConfigurationImportSelector 修飾的類來加載自動配置類的。繼續往下追蹤代碼,關注 getImports 方法:
1 /** 2 * Return the imports defined by the group. 3 * @return each import with its associated configuration class 4 */ 5 public Iterable<Group.Entry> getImports() { 6 for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { 7 this.group.process(deferredImport.getConfigurationClass().getMetadata(), 8 deferredImport.getImportSelector()); 9 } 10 return this.group.selectImports(); 11 }
關注第7行內的process方法,點進去會發現這是 DeferredImportSelector 類的內部接口 Group 的接口方法,它的實現類有兩個: AutoConfigurationImportSelector的內部類AutoConfigurationGroup 和 ConfigurationClassParser的內部類DefaultDeferredImportSelectorGroup ;這里執行到的代碼是 AutoConfigurationGroup ,代碼如下:
1 @Override 2 public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { 3 Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, 4 () -> String.format("Only %s implementations are supported, got %s", 5 AutoConfigurationImportSelector.class.getSimpleName(), 6 deferredImportSelector.getClass().getName())); 7 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) 8 .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); 9 this.autoConfigurationEntries.add(autoConfigurationEntry); 10 for (String importClassName : autoConfigurationEntry.getConfigurations()) { 11 this.entries.putIfAbsent(importClassName, annotationMetadata); 12 } 13 }
可以看到,這里傳入的參數必須是 AutoConfigurationImportSelector 實例,否則會拋出異常,所以EnableAutoConfiguration 注解類必須由 @Import(AutoConfigurationImportSelector.class) 修飾;進入第8行的 getAutoConfigurationEntry 方法:
1 /** 2 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata} 3 * of the importing {@link Configuration @Configuration} class. 4 * @param autoConfigurationMetadata the auto-configuration metadata 5 * @param annotationMetadata the annotation metadata of the configuration class 6 * @return the auto-configurations that should be imported 7 */ 8 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, 9 AnnotationMetadata annotationMetadata) { 10 if (!isEnabled(annotationMetadata)) { 11 return EMPTY_ENTRY; 12 } 13 AnnotationAttributes attributes = getAttributes(annotationMetadata); 14 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 15 configurations = removeDuplicates(configurations); 16 Set<String> exclusions = getExclusions(annotationMetadata, attributes); 17 checkExcludedClasses(configurations, exclusions); 18 configurations.removeAll(exclusions); 19 configurations = filter(configurations, autoConfigurationMetadata); 20 fireAutoConfigurationImportEvents(configurations, exclusions); 21 return new AutoConfigurationEntry(configurations, exclusions); 22 }
從方法名可以看到這里有加載配置類候選的邏輯,有過濾掉不需要加載的邏輯。我們這關注加載配置候選的邏輯,關於過濾(即條件加載機制)可以參考這篇文章 https://blog.csdn.net/weixin_33915554/article/details/89702088 ; 進入第14行的 getCandidateConfigurations 方法,如果看過SpringBoot源碼的同學,就會有一種熟悉的感覺了:
1 /** 2 * Return the auto-configuration class names that should be considered. By default 3 * this method will load candidates using {@link SpringFactoriesLoader} with 4 * {@link #getSpringFactoriesLoaderFactoryClass()}. 5 * @param metadata the source metadata 6 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation 7 * attributes} 8 * @return a list of candidate configurations 9 */ 10 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 11 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 12 getBeanClassLoader()); 13 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " 14 + "are using a custom packaging, make sure that file is correct."); 15 return configurations; 16 }
這里的 SpringFactoriesLoader.loadFactoryNames 方法,就是根據類名從spring.factories中查找對應的實現類名。我們還可以看一下 getSpringFactoriesLoaderFactoryClass 的代碼:
1 /** 2 * Return the class used by {@link SpringFactoriesLoader} to load configuration 3 * candidates. 4 * @return the factory class 5 */ 6 protected Class<?> getSpringFactoriesLoaderFactoryClass() { 7 return EnableAutoConfiguration.class; 8 }
沒錯,返回的就是 EnableAutoConfiguration.class 。好了,現在我們真實的從源碼看到了一些文章說的:EnableAutoConfiguration 注解類由 @Import(AutoConfigurationImportSelector.class) 修飾, SpringBoot從spring.facories中加載自動配置類的說法。
到這里為止,我們已經知道了SpringBoot獲取自動配置類全限定名的過程。回到 ConfigurationClassParser.DeferredImportSelectorGroupingHandler的processGroupImports 方法,繼續看拿到所有的自動配置類名后的操作,也就是forEach里面的內容,它解決一些循環依賴的問題,最終將所有的自動配置類信息都放到了 configurationClasses 中。 ConfigurationClassParser 的parse方法執行完畢后,回到 ConfigurationClassPostProcessor的processConfigBeanDefinitions 方法,繼續看下面一部分代碼。
1 // Parse each @Configuration class 2 ConfigurationClassParser parser = new ConfigurationClassParser( 3 this.metadataReaderFactory, this.problemReporter, this.environment, 4 this.resourceLoader, this.componentScanBeanNameGenerator, registry); 5 6 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); 7 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 8 do { 9 parser.parse(candidates); 10 parser.validate(); 11 12 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); 13 configClasses.removeAll(alreadyParsed); 14 15 // Read the model and create bean definitions based on its content 16 if (this.reader == null) { 17 this.reader = new ConfigurationClassBeanDefinitionReader( 18 registry, this.sourceExtractor, this.resourceLoader, this.environment, 19 this.importBeanNameGenerator, parser.getImportRegistry()); 20 } 21 this.reader.loadBeanDefinitions(configClasses); 22 alreadyParsed.addAll(configClasses);
第21行的 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitions 方法,將自動配置類解析成beanDefination,注冊到 registry 中;看一下代碼:
1 /** 2 * Read a particular {@link ConfigurationClass}, registering bean definitions 3 * for the class itself and all of its {@link Bean} methods. 4 */ 5 private void loadBeanDefinitionsForConfigurationClass( 6 ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { 7 8 if (trackedConditionEvaluator.shouldSkip(configClass)) { 9 String beanName = configClass.getBeanName(); 10 if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { 11 this.registry.removeBeanDefinition(beanName); 12 } 13 this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); 14 return; 15 } 16 17 if (configClass.isImported()) { 18 registerBeanDefinitionForImportedConfigurationClass(configClass); 19 } 20 for (BeanMethod beanMethod : configClass.getBeanMethods()) { 21 loadBeanDefinitionsForBeanMethod(beanMethod); 22 } 23 24 loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); 25 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); 26 }
自動配置類走的是第18行的邏輯,后續的功能主要是一些裝配BeanDefination的工作,這里不再詳述。
到這里為止,SpringBoot加載自動配置類的邏輯結束。