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~~

相关推荐
用户908324602732 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840822 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解2 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解2 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记3 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者3 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840823 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解3 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者4 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺4 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端