SpringBoot 啓動流程追蹤(第二篇)

上一篇文章分析了除 refresh 方法外的流程,並着重分析了 load 方法,這篇文章就主要分析 refresh 方法,可以說 refresh 方法是 springboot 啓動流程最重要的一環,沒有之一。我們通常在分析源碼的過程中,都需要帶着一個目標去看,不然看這看那,感覺什麼都沒有看一樣。這篇文章的目標在於弄懂 SpringBoot 自動裝配的原理,並且結合我們常用的 Mybatis-Plus 實戰分析一下。如果你不感興趣,可以直接跳過。

try {
	// Allows post-processing of the bean factory in context subclasses.
	postProcessBeanFactory(beanFactory);
	StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
	// Invoke factory processors registered as beans in the context.
	invokeBeanFactoryPostProcessors(beanFactory);
	// Register bean processors that intercept bean creation.
	registerBeanPostProcessors(beanFactory);
	beanPostProcess.end();
	// Initialize message source for this context.
	initMessageSource();
	// Initialize event multicaster for this context.
	initApplicationEventMulticaster();
	// Initialize other special beans in specific context subclasses.
	onRefresh();
	// Check for listener beans and register them.
	registerListeners();
	// Instantiate all remaining (non-lazy-init) singletons.
	finishBeanFactoryInitialization(beanFactory);
	// Last step: publish corresponding event.
	finishRefresh();
}

該方法是由 AnnotationConfigServletWebServerApplicationContext 類調用的,不過它也是調用的父類的方法,所以你可以直接挪到 AbstractApplicationContext 抽象類即可。

一、invokeBeanFactoryPostProcessors

if (beanFactory instanceof BeanDefinitionRegistry) {
	BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
	List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
	List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
	for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
		if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
			BeanDefinitionRegistryPostProcessor registryProcessor =
					(BeanDefinitionRegistryPostProcessor) postProcessor;
			registryProcessor.postProcessBeanDefinitionRegistry(registry);
			registryProcessors.add(registryProcessor);
		}
		else {
			regularPostProcessors.add(postProcessor);
		}
	}
	......
}

這裏的 beanFactory 是 DefaultListableBeanFactory,它是 BeanDefinitionRegistry 是實例,至於怎麼來的我想你應該清楚哈。然後在這片段代碼裏,我們比較注意的是 registryProcessor.postProcessBeanDefinitionRegistry(registry)。
這時你應該想到 beanFactoryPostProcessors 的值是怎麼來的,如果你往前追溯的話,可以知道是在 prepareContext 方法裏賦值的。另外需要注意到的是它分成了兩個類型的 processor,一個是 regularPostProcessors,另一個是 registryProcessors。

// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
		beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
	if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
		currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
		processedBeans.add(ppName);
	}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();

接着我們看這段代碼,這裏的註釋意思很清楚:不要在這裏實例化 FactoryBeans,我們需要把所有的 regular beans 留給 beanFactory 的 post-processors 去處理。然後將 BeanDefinitionRegistryPostProcessors 分離爲實現了 PriorityOrdered、Ordered 兩類,以及剩下的爲一類,然後調用 invokeBeanDefinitionRegistryPostProcessors 方法進行處理。

private static void invokeBeanDefinitionRegistryPostProcessors(
		Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
	for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
		StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
				.tag("postProcessor", postProcessor::toString);
		postProcessor.postProcessBeanDefinitionRegistry(registry);
		postProcessBeanDefRegistry.end();
	}

在這裏僅有一個實現了 PriorityOrdered 的 postProcessor,那就是 ConfigurationClassPostProcessor,這個是一個非常非常重要的 postProcessor,這裏不妨追溯一下它的數據來源,各位可以自己追溯看看,畢竟這是看源碼過程中一個必備的能力,答案就是在創建 context 的時候,調用 createApplicationContext 方法時,就設置值進去了,具體代碼如下:

context = createApplicationContext();
public AnnotationConfigServletWebServerApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
	registerAnnotationConfigProcessors(registry, null);

這個 registerAnnotationConfigProcessors 代碼過長,避免通篇都是代碼,這裏各位可以自己進去看看。
本文目的是分析自動裝配原理,並且該原理就藏在 ConfigurationClassPostProcessor 裏面,但是直接看代碼找還是很難的。這裏我們直接打斷點分析,首先我們來到 spring-boot-autoconfigure 包下,找到 AutoConfigurationImportSelector 類,至於爲什麼是該類,如果你看過 @EnableAutoConfiguration 註解的話就知道了,並且 @SpringBootApplication 也包含該註解。然後你可以找到如下代碼:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);

然後給它打上個斷點,開始調試,看看是怎麼跳進來的,下面只展示幾個流程:
1、獲取啓動類 Main 方法上的 Import 註解標註的類:AutoConfigurationImportSelector.class
image
2、獲取該類的 importGroup:AutoConfigurationGroup.class:
image
3、調用該類的 process:
image
4、獲取 @EnableAutoConfiguration 註解標註的類:
image
5、對 @EnableAutoConfigureation 標註的類進行處理
image
這裏看 processImports 方法,從這裏進去後追蹤到 doProcessConfigurationClass:

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
	if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
		return;
	}
	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 = asSourceClass(configClass, filter);
	do {
		sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
	}
	while (sourceClass != null);
	this.configurationClasses.put(configClass, configClass);
}



protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
		throws IOException {
	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// Recursively process any member (nested) classes first
		processMemberClasses(configClass, sourceClass, filter);
	}
	// Process any @PropertySource annotations
	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
	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
			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)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}
	// Process any @Import annotations
	processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
	// Process any @ImportResource annotations
	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
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}
	// Process default methods on interfaces
	processInterfaces(configClass, sourceClass);
	// Process superclass, if any
	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;
}

這裏就是怎麼處理這些 @EnableAutoConfigureation 標註的類,會先判斷類上是否包含 Component 註解,不要僅看表面,比如 @Configuration 註解也是包含 Component 註解的。如果包含的話會掃描其內部類,遞歸處理。總的來說:
1、掃描內部類判斷是否有資格註冊爲 bean。
2、如果包含 ComponentScan 等註解,會對包路徑進行掃描,掃描到的註冊爲 bean。
3、加載 @Import 註解標註的類,判斷其是否有資格註冊爲 bean。
4、加載 @ImportResource 標註的資源,註冊爲 bean。
5、加載 @Bean 註解標註的方法,註冊爲 bean。
6、加載接口方法、父類判斷是否有資格註冊爲 bean。
這裏說完還是比較抽象,我們不如以 mybatis-plus-boot-starter 作示例:

# Auto Configure
org.springframework.boot.env.EnvironmentPostProcessor=\
  com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

以上是 mybatis-plus 的 spring.factories,可以看到 EnableAutoConfiguration 註解的內容有哪些,這裏我們比較關注的應該是 MybatisPlusAutoConfiguration:
image
它擁有兩個內部類,按順序第一個沒有 component 註解,所以不會註冊爲 bean,第二個有 Configuration 註解、Import 註解,並且導入的是第一個類,還有一個 Conditional 註解,也就是是否有資格註冊成爲 bean。
image
現在讓我們看看執行順序是怎麼樣的,讓我們重新來到 doProcessConfigurationClass 方法,並且 configClass 是 MybatisPlusAutoConfiguration。
第一步會掃描到兩個內部類,至於怎麼處理後面再看,因爲這是一個遞歸函數。第二步會判斷該類是否包含 PropertySources 註解,這個名字一看就是處理類的屬性的。第三步會判斷其是否包含 ComponentScan 註解,遞歸掃描可被註冊的類。第四步就是掃描其 Import 的類,比如這裏導入的就是 AutoConfiguredMapperScannerRegistrar,但是其無法註冊爲 bean,因爲它不包含任何可被註冊爲 bean 的註解。後面的就不說了。這幾步流程完成後,Spring 已經註冊了絕大部分的 bean 了。但是並沒有發現 mybatis-plus 的 mapper bean 是什麼時候註冊的。
我們繼續往下看:
image
上述流程都是在 parser.parse(candidates) 中發生的,configClasses 就是我們解析到註冊的 bean,接下來就看看 this.reader.loadBeanDefinitions(configClasses) 方法:

private void loadBeanDefinitionsForConfigurationClass(
		ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
	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);
	}
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}
	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
	registrars.forEach((registrar, metadata) ->
			registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

這裏我們直接看到最後一段代碼,我們假設 configClass 是 MapperScannerRegistrarNotFoundConfiguration,那麼其 registrars 就是 AutoConfiguredMapperScannerRegistrar,這裏就會執行 registerBeanDefinitions 方法,該方法最後註冊了一個 processor:MapperScannerConfigurer,他會掃描 @Mapper 註解的 bean。
到這裏 invokeBeanDefinitionRegistryPostProcessors 的流程就介紹的差不多了,現在來看看後面的 invokeBeanFactoryPostProcessors 方法,現在主要看 ConfigurationClassPostProcessor 和 MapperScannerConfigurer 的調用。第一個其源碼註釋就是爲 bean 創建代理子類,爲 bean 的實例化作準備,而第二個確什麼都沒有做。那麼 @Mapper 註解標註的 bean 什麼時候實例化呢?這個目前不清楚什麼時候調用的,不過網上搜的是在 AbstractBeanFactory 類中實例化的,關鍵代碼如下:

				// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

二、registerBeanPostProcessors

該方法和上面的步驟也差不多,registerBeanPostProcessors 方法的作用是將所有的 BeanPostProcessor(後置處理器)註冊到 BeanFactory 中。

三、onRefresh

該方法會創建 WebServer,沒啥好說的。

四、finishBeanFactoryInitialization

finishBeanFactoryInitialization 法的作用是完成 BeanFactory 的初始化過程。在應用程序上下文啓動時,會調用該方法來完成 BeanFactory 的初始化工作,包括實例化和初始化所有的非延遲加載的單例 Bean。
具體來說,finishBeanFactoryInitialization 方法會按照以下步驟完成 BeanFactory 的初始化:

  1. 實例化所有非延遲加載的單例 Bean:根據 Bean 定義信息,通過反射或其他方式實例化所有非延遲加載的單例 Bean,並將它們放入容器中。
  2. 依賴注入:對所有實例化的 Bean 進行依賴注入,即將它們所依賴的其他 Bean 注入到相應的屬性中。
  3. 初始化:對所有實例化並注入依賴的 Bean 進行初始化。這包括調用 Bean 的初始化方法(如果有定義的話),以及應用任何配置的 Bean 後置處理器對 Bean 進行處理。
  4. 完成 BeanFactory 的初始化:將 BeanFactory 的狀態設置爲已初始化完成,標記整個初始化過程的結束。
    通過調用 finishBeanFactoryInitialization 方法,Spring 容器能夠確保所有非延遲加載的單例 Bean 都被正確實例化、注入依賴和初始化,從而使它們可以在應用程序中被正常使用。

五、finishRefresh

finishRefresh 方法的作用是在應用程序上下文刷新完成後執行一些額外的操作。
在應用程序上下文的刷新過程中,會調用該方法來完成一些與刷新相關的收尾工作。
具體來說,finishRefresh方法會按照以下步驟完成額外的操作:

  1. 初始化生命週期處理器:對於實現了 Lifecycle 接口的 Bean,會調用它們的 start 方法來啓動這些 Bean。
  2. 發佈應用程序上下文已完成事件:通過 ApplicationEventPublisher,發佈一個 ContextRefreshedEvent 事件,通知其他監聽器應用程序上下文已完成刷新。
  3. 註冊 ApplicationListener 的 Bean:將實現了 ApplicationListener 接口的 Bean 註冊到應用程序上下文中,以便監聽其他事件。
  4. 初始化其他單例 Bean:對於非延遲加載的單例 Bean,會調用它們的初始化方法(如果有定義的話)。
  5. 發佈應用程序上下文已啓動事件:通過 ApplicationEventPublisher,發佈一個 ContextStartedEvent 事件,通知其他監聽器應用程序上下文已啓動。

通過調用finishRefresh方法,Spring容器能夠在應用程序上下文刷新完成後執行一些額外的操作,如啓動生命週期處理器、發佈事件等。這些操作可以用於執行一些初始化、通知或其他自定義的邏輯,以滿足特定的需求。

6、總結

refresh 方法感覺還是比較難以分析,後面部分文字內容還是借鑑了 ChatGPT,感覺如果你想知道某個函數的作用時,直接問它,它或許會告訴你正確答案,比如你這樣問:finishRefresh 方法的作用是啥?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章