springboot自動掃描新增的BeanDefinition原始碼解析

wang03發表於2022-02-15

1.

springboot啟動過程中,首先會收集需要載入的bean的定義,作為BeanDefinition物件,新增到BeanFactory中去。

由於BeanFactory中只有getBean之類獲取bean物件的方法,所以將將BeanDefinition新增到BeanFactory中,是通過BeanDefinitionRegistry介面的void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;方法來完成的。

所以我們的BeanFactory的實現類如果需要具備通過beanName來返回bean物件和新增刪除BeanDefinition的能力,至少實現BeanFactoryBeanDefinitionRegistry的這兩個介面。

這裡我們就來看看springboot是如何查詢bean的定義,新增到BeanFactory中的。

由於我們這裡只是關注查詢bean物件的定義,所以這裡我們這裡提到的BeanFactory主要會關注BeanDefinitionRegistry這個介面。


我們本地主要分析springboot掃描載入bean的配置,和我們的程式碼關係不大,所以我們的程式碼就用最簡單的吧。具體程式碼如下:

package com.example.bootargs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootargsApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootargsApplication.class, args);
    }

}

後面提到的主類統一是com.example.bootargs.BootargsApplication

2.

Springboot 查詢bean的定義主要是通過ConfigurationClassPostProcessor這個類來完成的。

ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor介面。BeanDefinitionRegistryPostProcessor介面就是通過postProcessBeanDefinitionRegistry方法來給BeanDefinitionRegistry的實現類來新增bean的定義。

BeanDefinitionRegistryPostProcessor繼承了BeanFactoryPostProcessor介面,而BeanFactoryPostProcessor介面主要是用來對BeanFactory進行增強。在springboot啟動過程中首先會建立BeanFactory,再呼叫BeanFactoryPostProcessorBeanFactory

進行增強,最後才會去建立bean物件。

通過BeanFactoryPostProcessor對BeanFactory進行增強,主要是通過PostProcessorRegistrationDelegate的靜態方法來完成的。在這過程中就會呼叫到ConfigurationClassPostProcessor這個類。

由於ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor介面,PostProcessorRegistrationDelegate就會呼叫ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法中,就會呼叫到processConfigBeanDefinitions方法來查詢bean的定義。我們就從這裡作為入口來看吧。


3.

下面我們就去看看ConfigurationClassPostProcessorprocessConfigBeanDefinitions方法

	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		//在下面的這個for迴圈中,會從beanFactory中已經有的bean的定義中尋找有Configuration註解的配置類。
    //預設這裡獲取到的只有一個包含SpringBootApplication註解的主類
		for (String beanName : candidateNames) {
				......
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
    //如果沒有找到配置類,就直接返回
		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		......
		//在這裡就通過ConfigurationClassParser去解析配置類
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			//所有bean的定義的查詢都是在這裡完成的。下面我們去看看這裡的parse方法
      parser.parse(candidates);
			......
	}


ConfigurationClassParser中的parse方法中,由於我們的配置類是通過註解來定義的,所以會走AnnotatedBeanDefinition這個分支。繼續會呼叫到processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);這句,我們就直接進到這個processConfigurationClass方法去看吧。

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  	//在這裡首先看配置類上是否有Conditional註解,如果有的話,就去解析處理,看看是否要跳過這個註解類
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		
  	//所有解析出來的配置類都要放置到configurationClasses中,key是當前解析出來的配置類,value就是表示這個配置類是通過誰來匯入的。
  	//如果這個配置類不是通過別的類來匯入的,這時key和value就是一樣的。
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  	//如果通過多個配置類匯入了同一個配置類,那麼把這個和配置類的匯入關係就要進行一下合併
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
  	//這裡是將配置類轉化為SourceClass物件
		SourceClass sourceClass = asSourceClass(configClass, filter);
		do {
      //在這裡就會進行真正的配置類的解析出來。
      
      //注意這裡是個do-while迴圈,處理完當前的配置類,會繼續去處理當前配置類的父類。
      //如果當前類的父類類名不是java開頭,且沒有被處理過的話,就會在這個do-while迴圈中繼續去處理
      
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);
		
		this.configurationClasses.put(configClass, configClass);
	}

this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)這個的過濾主要是通過org.springframework.context.annotation.Condition介面的子類去實現matches方法完成的。

舉個例子簡單說下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration 

上面是MessageSourceAutoConfiguration類的定義,首先會查詢它上面的Conditional註解,會找到兩個註解:

  • @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)

    由於這個這個註解上面有@Conditional(OnBeanCondition.class),所以會交給OnBeanCondition這個類去處理。

  • @Conditional(ResourceBundleCondition.class),則會交給ResourceBundleCondition這個類去處理。


processConfigurationClass這個方法會有多個地方,主要會出現在三個地方:

  1. 就是呼叫parse方法的時候會呼叫到這個processConfigurationClass方法。
  2. doProcessConfigurationClass中解析當前配置類的屬性時也可能會多次呼叫到processConfigurationClass方法。
  3. this.deferredImportSelectorHandler.process()呼叫時也可能會呼叫到processConfigurationClass方法

我們這裡解析的所有配置類都新增到都會呼叫到configurationClasses.put(configClass, configClass)方法,所以我們最終有多個類新增到configurationClasses集合中,就至少有多少次呼叫到processConfigurationClass方法(有Conditional註解的判斷,所以呼叫次數可能多於最終新增到configurationClasses集合中元素個數)


	@Nullable
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		//在這裡,檢視類是否有Component註解,有的話,查詢當前類的內部類,進行處理
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
      //這裡就可能會遞迴呼叫到上面的processConfigurationClass方法
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
    //在這裡,檢視類是否有PropertySources註解,有的話去解析屬性配置,新增到環境上下文中去
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
    //在這裡,檢視類是否有ComponentScans註解,有的話,就根據這裡的條件去進行目錄掃描,查詢bean的定義
    //由於我們當前的類上有SpringBootApplication註解,所以這裡是能夠找到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) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
        //在這裡,就會去處理ComponentScans註解相關的內容。
        //ComponentScans註解上有個basePackages屬性,用來指定掃描的包的名字。
        //如果沒有指定basePackages屬性,就在當前類的包下及其所有子包下去查詢相關的bean的定義。
        //我們一般不會指定basePackages屬性,那麼會在當前sourceClass類的包及其所有子包下去查詢bean的定義。
        //我們自己程式碼中定義的controller,service,dao等等都是在這一步獲取到bean的定義的。
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {	
            //這裡也會間接呼叫到processConfigurationClass方法
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
    //在這裡,就會去處理類上的import註解。
    
    //getImports(sourceClass)首先會獲取到import的類。
    //這裡會有兩個,一個是AutoConfigurationPackage上註解的AutoConfigurationPackages.Registrar.class
    //另一個是EnableAutoConfiguration上的註解AutoConfigurationImportSelector.class)
    
    //下面我們去看看processImports這個方法
   	//這裡面也可能會呼叫到processConfigurationClass方法
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		......
	}

doProcessConfigurationClass是真正用來處理配置類的。

在這個方法中會依次處理內部類、PropertySources註解、ComponentScans註解、Import註解、ImportResource註解、Bean註解、介面上的預設方法、繼續遞迴到它的父類。

其中:

  • 內部類會繼續呼叫processConfigurationClass方法遞迴去處理

  • PropertySources註解解析後新增到環境上下文中

  • ComponentScans註解掃描到的到的類會直接被新增到beanFactory中,也會繼續呼叫processConfigurationClass方法遞迴去處理

  • Import註解會分3種情況處理:

    • Import的類如果實現了ImportSelector。且實現了它的子介面DeferredImportSelector,則會新增到deferredImportSelectors中,後續進行處理。如果沒有實現子介面,就遞迴呼叫processImports進行處理。
    • Import的類如果實現了ImportBeanDefinitionRegistrar。則新增到當前配置類的屬性中,進行後續處理。
    • 不屬於上面兩種情況的話,就繼續遞迴呼叫processConfigurationClass進行處理。
  • ImportResource註解、Bean註解、介面上的預設方法這些都會解析後新增到當前配置類的屬性上,後續進行處理


對下面方法的幾個入參簡單描述下:

  • configClasscurrentSourceClass這兩個引數直接都是指代我們包含SpringBootApplication註解的主類。

    其中configClass表示當前處理的類是被誰匯入的,currentSourceClass表示當前正在處理的類。這兩者一般底層是同一個資源類,但是有可能會有遞迴呼叫,這時兩者就可能會不同。

  • importCandidates是通過import註解匯入的類,這裡是AutoConfigurationPackages.Registrar.classAutoConfigurationImportSelector.class

    importCandidates就是當前被匯入的類,也就是在這裡被處理的類

  • exclusionFilter是在ConfigurationClassParser中定義的,用來過濾java.lang.annotation.org.springframework.stereotype.開頭的註解

  • checkForCircularImports表示是否檢查遞迴匯入

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
		
		if (importCandidates.isEmpty()) {
			return;
		}
		//這裡是錯誤檢查,檢查是否出現了遞迴
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
      //先將當前的配置類壓入棧
			this.importStack.push(configClass);
			try {
        //這裡,就會對import標籤匯入的類進行處理
				for (SourceClass candidate : importCandidates) {
          
          //AutoConfigurationImportSelector.class類就會走下面的分支
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
            //首先在這裡建立一個AutoConfigurationImportSelector類的物件,
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						if (selector instanceof DeferredImportSelector) {
            //在這裡,將當前的配置類和AutoConfigurationImportSelector的物件封裝成DeferredImportSelectorHolder物件
            //新增到延遲匯入的集合deferredImportSelectors中
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
          //AutoConfigurationPackages.Registrar.class這個類就會走到這個分支中
          //在這個分支中,首先建立AutoConfigurationPackages.Registrar的物件
          //新增到當前配置類的importBeanDefinitionRegistrars屬性中去
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

上面的import匯入類處理完了,下面我們繼續回到doProcessConfigurationClass中去看剩餘的部分

	@Nullable
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

			......//這部分前面已經分析過了,我們就繼續看後面的吧

		// Process any @ImportResource annotations
    // 這裡是處理ImportResource註解
		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);
			}
		}

		// Process individual @Bean methods
    //這裡是處理配置類內部的有Bean註解的方法,新增到配置類的beanMethods屬性中
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
    //這裡處理配置類實現的介面上預設方法上有Bean註解的話,也新增到beanMethods屬性中
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
    
    //這裡去獲取配置類的父類,如果存在父類且父類類名不是java開頭且還沒有被處理過,就會返回父類,繼續進行父類的處理。
		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;
	}

到這裡processConfigurationClass方法就整個分析完了。

下面就會走到parse方法的最後一句了。我們進去看看

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		......
		//就會走到下面這行程式碼
		this.deferredImportSelectorHandler.process();
	}

這裡主要是對延遲匯入的類進行處理

		public void process() {
			
			//在上面程式碼中我們分析到this.deferredImportSelectors中只有一個
      //由前面的配置類和AutoConfigurationImportSelector類的物件封裝的DeferredImportSelectorHolder物件
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
          //這裡會對延遲匯入的類進行分組,新增到handler中,由於我們這裡只有一個物件,所以這塊的分組,我們可以不用太關注
          //同時會將前面的配置類新增到handler物件的configurationClasses屬性中
					deferredImports.forEach(handler::register);
          //下面就會交給handler去進行處理
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
	}

下面我們看看processGroupImports是如何處理的

public void processGroupImports() {
  		//這裡就按分組去處理了
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        //這裡的grouping.getImports()就回去獲取系統的配置類,我們下面去看這個getImports
				grouping.getImports().forEach(entry -> {
				......
			}
		}

這裡的grouping.getCandidateFilter()來自兩部分:

  • 另一部分是來自ConfigurationClassParser定義的lambda表示式


這個是在ConfigurationClassParser類的一個靜態內部類DeferredImportSelectorGrouping中的方法

		public Iterable<Group.Entry> getImports() {
			//這裡的deferredImports中只有一個物件,還是之前的DeferredImportSelectorHolder
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        //這裡的this.group就是之前分組的deferredImport.getImportSelector().getImportGroup();方法的返回值建立的物件
        //具體就是AutoConfigurationImportSelector.AutoConfigurationGroup的物件
        //下面我們先看看這個process方法
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

process是在AutoConfigurationImportSelector.AutoConfigurationGroup這個類中

		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(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		//這裡,我們就能看到設定spring.boot.enableautoconfiguration屬性去禁止匯入系統配置的bean的定義
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}

		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //在下面這行中,就能看到通過ClassLoader去載入META-INF/spring.factories檔案,讀取內容。放置到cache中
    //在當前這裡,會去獲取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有屬性配置
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
    //在這裡獲取配置過濾類並建立物件,對上面的configuras進行過濾
    //這裡的配置過濾類也是從cache中獲取,key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
		configurations = getConfigurationClassFilter().filter(configurations);
    //這行程式碼不關鍵,我們可以不用去關注
		fireAutoConfigurationImportEvents(configurations, exclusions);
    //這裡返回一個AutoConfigurationEntry物件
    //其中configurations是過濾器能夠匹配到的配置類,exclusions在我們這裡是空的
		return new AutoConfigurationEntry(configurations, exclusions);
	}

上面程式碼中getConfigurationClassFilter()獲取到的是:

是來自spring.factories檔案中的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter

  • org.springframework.boot.autoconfigure.condition.OnClassCondition

    這個類主要檢查是否存在指定的類

  • org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

    這個類主要檢查是否存在WebApplicationContext.

  • org.springframework.boot.autoconfigure.condition.OnBeanCondition

    這個類主要檢查是否存在指定的bean

在這個過程中,在生成filter過程中,首先會通過類載入器去讀取META-INF/spring-autoconfigure-metadata.properties這些檔案。

在這裡,主要是通過類名.ConditionalOnBean類名.ConditionalOnSingleCandidate類名.ConditionalOnClass類名.ConditionalOnWebApplication來過濾掉不符合的配置類。

具體的演算法入口都在這3個類的父類FilteringSpringBootConditionmatch方法,具體的實現入口分別在這3個類的getOutcomes方法中。

由於這3個類都是實現了Condition介面,因此前面分析的 processConfigurationClass方法開始的地方通過 Conditional註解過濾配置類也會用到這3個類。

從上面也可以看出springboot的按需載入主要也是通過實現Condition介面來完成的。


再回到process這個方法。

		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			......//上面的程式碼剛才已經分析過了
      //在這裡將上面返回的AutoConfigurationEntry物件新增到autoConfigurationEntries中
			this.autoConfigurationEntries.add(autoConfigurationEntry);
      
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        //分別將新增的配置類新增到entries這個屬性中
        //importClassName是新查詢到的配置類,annotationMetadata都是同一個就是我們的主類
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

在接下來的selectImports方法中,首先會對這些新新增的配置類進行排序,然後組裝成new Entry(this.entries.get(importClassName), importClassName))物件的集合。

這裡需要注意的是this.entries.get(importClassName)這就是我們的主類,importClassName是我們需要新增的配置類。

這裡主要是為了對當前匯入的配置類和它是被誰匯入的進行一個關聯(在這裡,所有要匯入的配置類都是由我們的主類來匯入的)。

就是在後面建立ConfigurationClass物件時會使用public ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy)這個構造方法。

最後在新增這些配置類到beanFactory中時通過


下面再回到processGroupImports方法

	public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
				//上面已經分析到grouping.getImports()返回的是Entry物件的集合
				grouping.getImports().forEach(entry -> {
          //entry.getMetadata()返回的還是我們之前的主類。
          //這裡的configurationClass也是我們之前的主類。
          //這個主要是為了在processImports方法中建立的配置類為它們設定importedBy屬性
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
            //這裡又會呼叫到processImports這個方法。這個在前面已經分析過了,但是這裡有一點不一樣,下面我們看看不一樣的地方
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}


關於這個processImports方法的引數前面有描述,這裡就不再說了

下面的這個方法中這時importCandidates和之前的有點不一樣,之前的是通過import註解匯入的分別會走for迴圈的前面兩個分支,現在大概率會走到後面的else分支


	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
					if (candidate.isAssignable(ImportSelector.class)) {
					......
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
					......	
					}
					else {

						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
            //上次進入這個方法,分別走了上面的兩個分支,現在大概率會走到這個分支
            //這裡會將匯入的類新增到imports屬性中,key是新匯入的配置類,value是我們之前的主類
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            //這裡又會去處理新新增的配置類,在這裡是有可能出現遞迴的,下面我們具體分析下這裡的處理邏輯
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			......
		}
	}

在上面的processImports方法中,會處理新新增的配置類,會呼叫到processConfigurationClass這個方法。


到上面為止,ConfigurationClassPostProcessorprocessConfigBeanDefinitions方法從parse處理的部分就全部分析完了 。

這部分主要是處理了通過主類上面的註解,將所有的配置類都新增到ConfigurationClassParser類的成員變數configurationClasses中。對於配置類上的ImportResourceBean等等則新增配置類的對應的屬性上。

這裡需要注意的是在整個整個過程中只有ComponentScans掃描到的配置類會新增到beanFactory中。

下面我們繼續看看後面的程式碼。

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ......
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			parser.parse(candidates);//前面已經分析到了這裡
			parser.validate();

      //這裡就會得到所有的配置類
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      //alreadyParsed第一次是空的,由於這個方法是do-while迴圈,在後面會對這個變數賦值
			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());
			}
      //在這裡就會對前面獲取的所有的配置類新增到beanFactory中
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			candidates.clear();
      //這裡就是對比前後beanFactory中的beanDefinition數量是否有增加,如果有增加說明我們在本次do-while程式碼中新增了beanFactory
      //下面的邏輯主要是為了判斷當前掃描出來的配置類是否全部新增進了beanFactory中,如果有配置類還沒有被今天進去,就會迴圈,重新執行上面的邏輯
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}

上面的其他程式碼都比較簡單,我們下面主要對上面的this.reader.loadBeanDefinitions(configClasses);做個簡單分析吧。


ConfigurationClassBeanDefinitionReader的方法

	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		
		//這個類還是用來對Conditional註解進行處理,來判斷當前配置類是否要被過濾掉
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			//在這裡會對每個配置類及它的屬性進行處理,封裝成beanDefinition新增到beanFactory中去
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
		
    //這裡就會對Conditional註解進行判斷,如果當前類是被匯入的,就會去判斷匯入它的類
		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
		//如果類是被匯入的,就會去對它進行處理
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
    //下面就是對配置類的各種屬性進行處理
    //處理方法上的bean註解
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
		//處理匯入的資源
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    //處理匯入的ImportBeanDefinitionRegistrar
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

在上面的程式碼也可以看到,單純的配置類,如果configClass.isImported()返回false,就不會被新增到beanFactory中。也就是如果配置類不是被匯入的,就不會將配置類新增到beanFactory中。

前面說過ComponentScans掃描到的類在處理過程中就被新增到了beanFactory中,其他的配置類都是在上面的方法中被新增進去的。

所有新增的類大致可以分為兩部分:

  1. 通過類上的註解,直接被新增到配置類中。這部分配置類它們的被匯入類就是當前的主類。
  2. 另一部分是通過主類上的@Import(AutoConfigurationImportSelector.class)註解,讀取META-INF/spring.factories檔案,經過META-INF/spring-autoconfigure-metadata.properties檔案過濾後被處理的類。

上面兩部分處理的時候都會進行遞迴,一層一層處理。而且所有的處理過程中也都會根據 Conditional註解進行過濾。

同時也需要注意雖然新增到beanFactory中的都是beanD,但是具體都是不一樣的。比如:

ScannedGenericBeanDefinition是通過ComponentScans註解新增的

ConfigurationClassBeanDefinition是處理方法上的bean註解新增的

AnnotatedGenericBeanDefinition是其他普通的配置類


到上面,整個分析就結束了。

整個過程涉及到的各種遞迴呼叫等等都比較多,為了不至於文章顯的太分散,上面分析過程中對很多細節也都進行了省略。

由於個人能力問題,上面的分析可能存在錯誤或者描述不清晰的地方,歡迎大家評論指正。

相關文章