Springboot3 自动装配流程与核心文件:imports文件

注:本文以spring-boot v3.4.1源码为基础,梳理spring-boot应用启动流程、分析自动装配的原理

如果对spring-boot2自动装配有兴趣,可以看看我另一篇文章:
Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring.factories(SPI机制核心)

1、启动入口

以下是源码里一段应用启动单元测试代码:

复制代码
package org.springframework.boot.test.autoconfigure;
...

/**
 * Tests for {@link ConditionReportApplicationContextFailureProcessor}.
 *
 * @author Phillip Webb
 * @author Scott Frederick
 * @deprecated since 3.2.11 for removal in 3.6.0
 */
@ExtendWith(OutputCaptureExtension.class)
@Deprecated(since = "3.2.11", forRemoval = true)
@SuppressWarnings("removal")
class ConditionReportApplicationContextFailureProcessorTests {

	@Test
	void loadFailureShouldPrintReport(CapturedOutput output) {
		SpringApplication application = new SpringApplication(TestConfig.class);
		application.setWebApplicationType(WebApplicationType.NONE);
		ConfigurableApplicationContext applicationContext = application.run();
		ConditionReportApplicationContextFailureProcessor processor = new ConditionReportApplicationContextFailureProcessor();
		processor.processLoadFailure(applicationContext, new IllegalStateException());
		assertThat(output).contains("CONDITIONS EVALUATION REPORT")
			.contains("Positive matches")
			.contains("Negative matches");
	}

	@Configuration(proxyBeanMethods = false)
	@ImportAutoConfiguration(JacksonAutoConfiguration.class)
	static class TestConfig {
	}
}

spring-boot3应用启动入口是SpringApplication的构造方法,这个构造方法里做了一些初始化,比较重要。如下:

复制代码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		// @A
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// @B
		this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());
		// @C
		this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
  • @A:标签当前应用的启动主类,也就是我们平常写的xxxApplication类

  • @B:在类路径下查找是否有 :

    • private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    • private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    • private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
      中的一个,标记当前web应用类型;web应用类型有:REACTIVE SERVLET NONE
  • @C:从类路径中可见的 spring.factories 文件中获取配置的BootstrapRegistryInitializer.class、ApplicationContextInitializer.class、ApplicationListener.class并缓存

2、启动核心方法 public ConfigurableApplicationContext run(String... args){}
复制代码
public ConfigurableApplicationContext run(String... args) {
		Startup startup = Startup.create();
		if (this.properties.isRegisterShutdownHook()) {
			SpringApplication.shutdownHook.enableShutdownHookAddition();
		}
        // @A
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		// @B
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
    		// @C
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			Banner printedBanner = printBanner(environment);
             // @D
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			// @E
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// @F
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			startup.started();
			if (this.properties.isLogStartupInfo()) {
				new StartupInfoLogger(this.mainApplicationClass, environment).logStarted(getApplicationLog(), startup);
			}
			listeners.started(context, startup.timeTakenToStarted());
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			throw handleRunFailure(context, ex, listeners);
		}
		try {
			if (context.isRunning()) {
				listeners.ready(context, startup.ready());
			}
		}
		catch (Throwable ex) {
			throw handleRunFailure(context, ex, null);
		}
		return context;
	}
3、prepareEnvironment方法:创建ConfigurableEnvironment

接【第2章节】 @C 代码:

复制代码
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// @A
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// @B
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		// @C
		listeners.environmentPrepared(bootstrapContext, environment);
		ApplicationInfoPropertySource.moveToEnd(environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

跟踪了一下代码:

  • @A:方法里执行了 new ApplicationEnvironment()创建对象并返回了,在构造方法里会触发StandardEnvironment#customizePropertySources方法,这个方法会把System.getProperties()的值放到环境的systemProperties环境值,然后把ProcessEnvironment.getenv()的值放到systemEnvironment环境值
  • @B:配置spring应用环境信息,解析启动命令里的参数
    • 创建一个ApplicationConversionService对象赋值到 ApplicationEnvironment的ConversionService属性;ApplicationConversionService主要作用是提供类型转换服务,可以将A类型数据转换为B类型数据。 细节可以参看这里: ConversionService介绍
    • 如果在启动参数中加了commandLineArgs参数,并且属性源(PropertySource)有commandLineArgs这个名称,则会在Environment里进行真实PropertySource替换;(这一块没太看懂具体要做的目的)
    • 最后创建一个ApplicationInfoPropertySource对象到ApplicationEnvironment的PropertySource链表中
  • @C:执行在【第2章节】创建的SpringApplicationRunListeners的environmentPrepared方法
4、prepareContext方法:准备各种Context,拉齐属性

接【第2章节】,DefaultBootstrapContext 是spring-boot的类,而 ConfigurableApplicationContext是spring-frampwork的类;这个方法会把两个类关联起来

复制代码
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
	    // @A
		context.setEnvironment(environment);
		// @B
		postProcessApplicationContext(context);
		addAotGeneratedInitializerIfNecessary(this.initializers);
		// @C
		applyInitializers(context);
		// @D
		listeners.contextPrepared(context);
		bootstrapContext.close(context);
		if (this.properties.isLogStartupInfo()) {
			logStartupInfo(context.getParent() == null);
			logStartupInfo(context);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		// @E
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
			autowireCapableBeanFactory.setAllowCircularReferences(this.properties.isAllowCircularReferences());
			if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
				listableBeanFactory.setAllowBeanDefinitionOverriding(this.properties.isAllowBeanDefinitionOverriding());
			}
		}
		if (this.properties.isLazyInitialization()) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		if (this.properties.isKeepAlive()) {
			context.addApplicationListener(new KeepAlive());
		}
		// @F
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		if (!AotDetector.useGeneratedArtifacts()) {
			// Load the sources
			Set<Object> sources = getAllSources();
			Assert.notEmpty(sources, "Sources must not be empty");
			load(context, sources.toArray(new Object[0]));
		}
		// @G
		listeners.contextLoaded(context);
	}
  • @A:将刚刚创建的环境信息对象,设置到spring context里(即AnnotationConfigApplicationContext对象),使其和spring-boot的环境信息ConfigurableEnvironment对象保持一致
  • @B:这里继续对齐两个Context的属性,包括:
    • 1、向spring context注入bean定义:name为org.springframework.context.annotation.internalConfigurationBeanNameGenerator
    • 2、设置spring context的ResourceLoader和ClassLoader属性
    • 3、对齐sping容器属性:ConversionService conversionService,这个对象是在【第3章节】@B时机创建的一个ApplicationConversionService对象
  • @C:获取所有的ApplicationContextInitializer对象,并执行initialize方法;在initialize方法可以向ConfigurableListableBeanFactory bean工厂里注入一些自定义的bean定义或者其他bean工厂处理
  • @D:执行所有SpringApplicationRunListener子类的contextPrepared方法,框架默认提供的SpringApplicationRunListener子类是EventPublishingRunListener,是在【第2章节】@B位置创建的对象;SpringApplicationRunListeners实则是对SpringApplicationRunListener集合的封装,两者相差一个字母 s
  • @E:拿到beanFactory,一顿自定义操作
  • @F:注册BeanFactory后置处理器
  • @G:调用所有SpringApplicationRunListener子类的 contextLoaded方法,通知context已加载完毕
建议

在看源码的时候有一些关键类需要注意一下DefaultBootstrapContext bootstrapContext是springboot提供的, ConfigurableApplicationContext context是spring-framework 提供的,这样就会更清晰一点;

todo~~

相关推荐
cainiao0806051 小时前
《Spring Boot 4.0新特性深度解析》
java·spring boot·后端
呆萌很2 小时前
基于 Spring Boot 瑞吉外卖系统开发(十二)
spring boot
计算机学姐2 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven
小鸡脚来咯2 小时前
请求参数:Header 参数,Body 参数,Path 参数,Query 参数分别是什么意思,什么样的,分别通过哪个注解获取其中的信息
java·spring boot·后端
添砖Java中4 小时前
深入剖析缓存与数据库一致性:Java技术视角下的解决方案与实践
java·数据库·spring boot·spring·缓存·双写一致性
幽络源小助理5 小时前
懒人美食帮SpringBoot订餐系统开发实现
java·spring boot·后端·美食
源码云商7 小时前
基于Spring Boot + Vue的母婴商城系统( 前后端分离)
java·spring boot·后端
还听珊瑚海吗11 小时前
基于SpringBoot的抽奖系统测试报告
java·spring boot·后端
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧14 小时前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
曼岛_16 小时前
[Java实战]Spring Boot 静态资源配置(十三)
java·开发语言·spring boot