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

相关推荐
奈葵34 分钟前
Spring Boot/MVC
java·数据库·spring boot
落霞的思绪35 分钟前
Redis实战(黑马点评)——涉及session、redis存储验证码,双拦截器处理请求
spring boot·redis·缓存
liuyunshengsir2 小时前
Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能
java·spring boot·prometheus
何中应2 小时前
Spring Boot中选择性加载Bean的几种方式
java·spring boot·后端
2013crazy3 小时前
Java 基于 SpringBoot+Vue 的校园兼职平台(附源码、部署、文档)
java·vue.js·spring boot·兼职平台·校园兼职·兼职发布平台
michael.csdn3 小时前
Spring Boot & MyBatis Plus 版本兼容问题(记录)
spring boot·后端·mybatis plus
问道飞鱼4 小时前
【Springboot知识】Springboot结合redis实现分布式锁
spring boot·redis·分布式
码明4 小时前
SpringBoot整合ssm——图书管理系统
java·spring boot·spring
荆州克莱5 小时前
微信小程序获取位置服务
spring boot·spring·spring cloud·css3·技术