注:本文以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 {@Testvoid 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");// @Athis.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// @Bthis.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());// @Cthis.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();}// @ADefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();// @BSpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {// @CApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner = printBanner(environment);// @Dcontext = createApplicationContext();context.setApplicationStartup(this.applicationStartup);// @EprepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// @FrefreshContext(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;}
- @A:创建DefaultBootstrapContext对象,逐个执行前面缓存中bootstrapRegistryInitializers的initialize方法
- @B:从类路径META-INF/spring.factories配置文件中获取SpringApplicationRunListener配置的类(框架默认提供了EventPublishingRunListener)封装成SpringApplicationRunListeners,然后执行SpringApplicationRunListeners的starting方法,最终调用的是EventPublishingRunListener等配置类的starting方法
- @C:创建ConfigurableEnvironment ,代码参看下面【第3章节】的prepareEnvironment方法
- @D:创建ConfigurableApplicationContext对象,实现具体代码是:org.springframework.boot.DefaultApplicationContextFactory#create,默认情况下是返回的new AnnotationConfigApplicationContext() 这个对象
- @E:准备各种Context,详情见下面代码【第4章节】的prepareContext方法
- @F:刷新容器,这一块逻辑主要是执行spring-framework原生的refresh方法;这个核心方法主要做的内容就是解析bean定义,然后执行整个bean生命周期;具体流程可以看我的另一篇文章:
- 链接: Spring 容器初始化源码跟读refresh01
- 链接: Spring 容器初始化源码跟读refresh02
- 链接: Spring 容器初始化源码跟读refresh03
- 链接: Spring 容器初始化源码跟读refresh04
- 链接: Spring 容器初始化源码跟读refresh05
- 链接: Spring 容器初始化源码跟读refresh06
- 链接: Spring 容器初始化源码跟读refresh07
在refresh阶段,就会执行spring-boot的自动装配的整个过程;
3、prepareEnvironment方法:创建ConfigurableEnvironment
接【第2章节】 @C 代码:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environment// @AConfigurableEnvironment environment = getOrCreateEnvironment();// @BconfigureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);// @Clisteners.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) {// @Acontext.setEnvironment(environment);// @BpostProcessApplicationContext(context);addAotGeneratedInitializerIfNecessary(this.initializers);// @CapplyInitializers(context);// @Dlisteners.contextPrepared(context);bootstrapContext.close(context);if (this.properties.isLogStartupInfo()) {logStartupInfo(context.getParent() == null);logStartupInfo(context);logStartupProfileInfo(context);}// Add boot specific singleton beans// @EConfigurableListableBeanFactory 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());}// @Fcontext.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));if (!AotDetector.useGeneratedArtifacts()) {// Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));}// @Glisteners.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已加载完毕
自动装配
以上内容只是梳理了spring-boot应用启动的大致流程,那么自动装配发生在什么阶段呢?这里可以看看之前我对spring-boot2自动装配梳理的文章: Springboot2 自动装配之spring-autoconfigure-metadata.properties和spring.factories(SPI机制核心)
同样spring-boot3自动装配也是看@SpringBootApplication这个注解,该注解是一个复合注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
再看看EnableAutoConfiguration 这个注解:
@AutoConfigurationPackage
@Import(ImportAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
被@Import的是一个AutoConfigurationImportSelector类和spring-boot2一样,但是实现细节不一样了,增加了从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里获取配置类,代码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// @AList<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);}
- @A 从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置获取自动装配类(这是和spring-boot2差异比较大的一点)
- spring-boot3并不兼容spring.factories配置自动装配类,这一点个人觉得很不友好,因为升级后原来自定义的装配类都要重新迁移一遍
- 通过断点看出来,这里获取自动装配类的时机是由bean工厂后置处理器触发:
其他自动装配流程和spring-boot2差不多,可以看看我之前文章。
建议
在看源码的时候有一些关键类需要注意一下DefaultBootstrapContext bootstrapContext是springboot提供的, ConfigurableApplicationContext context是spring-framework 提供的,这样就会更清晰一点;
over~~