写点什么

详细分析 Spring Boot 启动流程

作者:Java你猿哥
  • 2023-03-27
    湖南
  • 本文字数:13868 字

    阅读完需:约 45 分钟

详细分析Spring Boot启动流程

组里的实习生妹妹讲了一次 Springboot 的启动,

讲着讲着就讲到 Spring bean 的生命周期去了,

我心想坏了,

这妮子估计把 Springboot Spring 的相关逻辑给混淆了,

这必须得给她治一治。


前言

本文会对 Springboot 启动流程进行详细分析。但是请注意,Springboot 启动流程是 Springboot 的逻辑,请千万不要将 Springboot 启动流程相关逻辑与 Spring 的相关逻辑混在一起,比如把 Spring bean 生命周期的逻辑混在 Springboot 启动流程中,那么整个体系就复杂且混乱了。

所以本文仅重点关注 Springboot 启动流程,涉及 Spring 的部分,会略作说明并跳过。

整体的一个结构图如下。


Springboot 版本:2.4.1

正文

一. Springboot 启动流程图及说明

如下是 Springboot 的一个启动流程图。


SpringApplication 完成初始化后,就会调用 SpringApplication 对象的 run() 方法,该方法就是 Springboot 启动的入口,也对应着全流程图中的开始。下面给出 SpringApplication 对象的 run() 方法说明,如下所示。

public ConfigurableApplicationContext run(String... args) {    // 创建StopWatch,用于统计Springboot启动的耗时    StopWatch stopWatch = new StopWatch();    // 开始计时    stopWatch.start();    DefaultBootstrapContext bootstrapContext = createBootstrapContext();    ConfigurableApplicationContext context = null;    configureHeadlessProperty();    // 获取运行时监听器    SpringApplicationRunListeners listeners = getRunListeners(args);    // 调用运行时监听器的starting()方法    // 该方法需要在Springboot一启动时就调用,用于特别早期的初始化    listeners.starting(bootstrapContext, this.mainApplicationClass);    try {        // 获取args参数对象        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);        // 读取Springboot配置文件并创建Environment对象        // 这里创建的Environment对象实际为ConfigurableEnvironment        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);        configureIgnoreBeanInfo(environment);        // 打印Banner图标        Banner printedBanner = printBanner(environment);        // 创建ApplicationContext应用行下文,即创建容器        context = createApplicationContext();        context.setApplicationStartup(this.applicationStartup);        // 准备容器        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);        // 初始化容器        refreshContext(context);        afterRefresh(context, applicationArguments);        // 停止计时        stopWatch.stop();        if (this.logStartupInfo) {            // 打印启动耗时等信息            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);        }        // 调用运行时监听器的started()方法        // 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行        listeners.started(context);        callRunners(context, applicationArguments);    }    catch (Throwable ex) {        handleRunFailure(context, ex, listeners);        throw new IllegalStateException(ex);    }
try { // 调用运行时监听器的running()方法 // 该方法需要在SpringApplication的run()方法执行完之前被调用 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context;}
复制代码

二. SpringApplication 的初始化

通常,Springboot 应用程序的启动类定义如下。

@SpringBootApplicationpublic class LearnStartApplication {
public static void main(String[] args) { SpringApplication.run(LearnStartApplication.class, args); }
}
复制代码

SpringApplication 的静态 run() 方法一路跟进,会发现如下的实现。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {    return run(new Class<?>[] { primarySource }, args);}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
复制代码

也就是 Springboot 启动时会先创建 SpringApplication,然后再通过 SpringApplication run() 方法完成启动。所以下面分析一下 SpringApplication 的初始化逻辑,其构造方法如下所示。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {    this.resourceLoader = resourceLoader;    Assert.notNull(primarySources, "PrimarySources must not be null");    // 设置源    // 通常Springboot的启动类就是源    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));    // 推断并设置WEB应用程序类型    // 根据classpath下的类来推断    this.webApplicationType = WebApplicationType.deduceFromClasspath();    // 加载并设置Bootstrapper    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));    // 加载并设置初始化器    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));    // 加载并设置应用事件监听器    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    // 推断并设置应用程序主类的Class对象    this.mainApplicationClass = deduceMainApplicationClass();}
复制代码

梳理一下在 SpringApplication 的构造方法中,做了如下事情。

  1. 设置源。通常,Springboot 中的源就是 Springboot 的启动类;

  2. 设置 WEB 应用程序类型。通过判断 classpath 下是否存在某些类,来推断当前 WEB 应用程序的类型;

  3. 加载并设置 BootstrapperApplicationContextInitializer ApplicationListener。借助 SpringFactoriesLoader 基于 SPI 机制完成 BootstrapperApplicationContextInitializer ApplicationListener 的加载,然后设置到 SpringApplication 中;

  4. 设置应用程序主类的 Class 对象。

下面对上述事情进行分析。

1. 设置源

这里的源,也就是 Spring 容器启动时依赖的初始配置类,在 Springboot 中,初始配置类通常为启动类。下面可以通过调试看一下 primarySources 字段的值,如下所示。


可见源就是 Springboot 的启动类的 Class 对象。

2. 设置 WEB 应用程序类型

WebApplicationType#deduceFromClasspath 方法如下所示。

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",			"org.springframework.web.context.ConfigurableWebApplicationContext" };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";private static final String SERVLET_APPLICATION_CONTEXT_CLASS                     = "org.springframework.web.context.WebApplicationContext";private static final String REACTIVE_APPLICATION_CONTEXT_CLASS                     = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { // classpath下存在DispatcherHandler,但不存在DispatcherServlet和ServletContainer // 则WEN应用程序类型推断为REACTIVE,即响应式WEB应用程序 return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { // 非WEB应用程序 return WebApplicationType.NONE; } } // 基于Servlet的WEB应用程序 return WebApplicationType.SERVLET;}
复制代码

WebApplicationType 中预定义了若干种用于判断的类的全限定名,然后在 deduceFromClasspath() 方法中使用 ClassUtils 来判断预定义的类是否存在,通过这样的方式最终可以推断出当前 WEB 应用程序类型。在示例工程中,如果只引入 spring-boot-starter 包,那么推断出来的 WebApplicationType NONE,如下所示。


如果再引入 spring-boot-starter-web 包,则推断出来的 WebApplicationType SERVLET,如下所示。


3. 加载并设置 Bootstrapper,ApplicationContextInitializer 和 ApplicationListener

这里主要分析一下是如何加载 BootstrapperApplicationContextInitializer ApplicationListener 的。它们的加载均使用了 getSpringFactoriesInstances() 方法,下面看一下实现。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {    return getSpringFactoriesInstances(type, new Class<?>[] {});}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // 通过SpringFactoriesLoader扫描classpath所有jar包的META-INF目录下的spring.factories文件 // 将type全限定名对应的全限定名的集合获取到 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 实例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances;}
复制代码

主要就是基于 SpringFactoriesLoader 完成加载,加载机制和 Springboot 中的自动装配是一样,唯一的区别就是自动装配中在 spring.factories 文件中是根据 @EnableAutoConfiguration 的全限定名作为 key 去获取全限定名集合,而在这里是根据 BootstrapperApplicationContextInitializer ApplicationListener 的全限定名作为 key 去获取全限定名集合,以 spring-boot-autoconfigure 包中的 spring.factories 文件为例,说明如下。

org.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
org.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer
复制代码

4. 设置应用程序主类的 Class 对象

获取应用程序主类的 Class 对象的 SpringApplication#deduceMainApplicationClass 方法如下所示。

private Class<?> deduceMainApplicationClass() {    try {        // 通过RuntimeException获取堆栈        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();        for (StackTraceElement stackTraceElement : stackTrace) {            // 判断堆栈元素的发生方法名是否为main            if ("main".equals(stackTraceElement.getMethodName())) {                // 通过反射获取到main方法所在类的Class对象                return Class.forName(stackTraceElement.getClassName());            }        }    }    catch (ClassNotFoundException ex) {            }    return null;}
复制代码

获取应用程序主类的 Class 对象是通过堆栈实现的,下面给出调试截图。


三. Springboot 事件机制

Springboot 启动的一开始,有一步逻辑是获取运行时监听器,最终会获取到一个 SpringApplicationRunListeners 对象,下面看一下获取运行时监听器的 getRunListeners() 方法的实现。

private SpringApplicationRunListeners getRunListeners(String[] args) {    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };    // 先基于SpringFactoriesLoader的SPI机制获取SpringApplicationRunListener的实现类集合    // 然后创建SpringApplicationRunListeners对象    return new SpringApplicationRunListeners(logger,            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),            this.applicationStartup);}
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners, ApplicationStartup applicationStartup) { // SpringApplicationRunListeners的构造方法中只是进行简单赋值 this.log = log; this.listeners = new ArrayList<>(listeners); this.applicationStartup = applicationStartup;}
复制代码

getRunListeners() 方法中会先基于 SpringFactoriesLoader SPI 机制将 SpringApplicationRunListener 接口的实现类获取出来,在 spring-boot 包中提供了一个 SpringApplicationRunListener 接口的实现类,为 EventPublishingRunListener,这也是 Springboot 提供的唯一一个内置运行时监听器,所以通过 getRunListeners() 方法获取到的 SpringApplicationRunListeners 对象中持有一个 SpringApplicationRunListener 的集合,这个集合中默认情况下一定会包含一个 EventPublishingRunListener 的对象。


下面再以 SpringApplicationRunListeners starting() 方法为例,分析一下 SpringApplicationRunListeners 是如何工作的。

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {    doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),            (step) -> {                if (mainApplicationClass != null) {                    step.tag("mainApplicationClass", mainApplicationClass.getName());                }            });}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) { StartupStep step = this.applicationStartup.start(stepName); // 集合中每个运行时监听器都会执行listenerAction函数 this.listeners.forEach(listenerAction); if (stepAction != null) { stepAction.accept(step); } step.end();}
复制代码

结合

SpringApplicationRunListeners starting() 和 doWithListeners() 方法,可知 SpringApplicationRunListeners 会将 starting() 方法的调用传递给其持有的每个运行时监听器,所以


SpringApplicationRunListeners 是组合模式的一个应用。


那么 Springboot 中的事件机制按理应该由 Springboot 提供的唯一一个运行时监听器 EventPublishingRunListener 实现。下面分析

EventPublishingRunListener 的逻辑,还是以

EventPublishingRunListener starting() 方法为例,进行说明。

public void starting(ConfigurableBootstrapContext bootstrapContext) {    // 先创建一个ApplicationStartingEvent事件对象    // 然后调用SimpleApplicationEventMulticaster来发布事件对象    this.initialMulticaster            .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));}
复制代码

EventPublishingRunListener starting() 方法中会先创建 ApplicationStartingEvent 事件对象,然后通过

EventPublishingRunListener 持有的一个

SimpleApplicationEventMulticaster 对象来发布事件。


那么下面继续分析 SimpleApplicationEventMulticaster 怎么发布事件,发布给谁,SimpleApplicationEventMulticaster multicastEvent() 方法如下所示。

public void multicastEvent(ApplicationEvent event) {    multicastEvent(event, resolveDefaultEventType(event));}
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); // 调用getApplicationListeners()方法将所有适合接收当前事件的ApplicationListener获取出来 // 然后基于异步或者同步的方式向符合条件的ApplicationListener发布事件 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { // 异步发布 executor.execute(() -> invokeListener(listener, event)); } else { // 同步发布 invokeListener(listener, event); } }}
复制代码

SimpleApplicationEventMulticaster multicastEvent() 方法中会先将初始化 SpringApplication 时加载的 ApplicationListener 获取到,然后遍历其中适合接收当前事件的 ApplicationListener,然后异步或者同步的向 ApplicationListener 发布事件,继续看 invokeListener() 方法,如下所示。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {    ErrorHandler errorHandler = getErrorHandler();    // 实际调用doInvokeListener()方法来向ApplicationListener发布事件    if (errorHandler != null) {        try {            doInvokeListener(listener, event);        }        catch (Throwable err) {            errorHandler.handleError(err);        }    }    else {        doInvokeListener(listener, event);    }}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { // ApplicationListener接口的实现类都会实现onApplicationEvent()方法 // 在onApplicationEvent()方法中会处理当前接收到的事件 listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass())) { Log logger = LogFactory.getLog(getClass()); if (logger.isTraceEnabled()) { logger.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } }}
复制代码

SimpleApplicationEventMulticaster invokeListener() 方法中实际会调用到 doInvokeListener() 方法,在 doInvokeListener() 方法中会调用 ApplicationListener onApplicationEvent() 方法,所以在这里就调用到了 ApplicationListener 实际处理事件的逻辑。

现在对 Springboot 中的事件监听机制进行小结。

  1. SpringApplication 初始化时会加载所有的 ApplicationListener

  2. Springboot 启动的一开始,会调用到 SpringApplication#getRunListeners 方法创建一个 SpringApplicationRunListeners 对象;

  3. SpringApplicationRunListeners 是组合模式的应用,其持有一个 SpringApplicationRunListener 的集合,集合中默认会存在一个 Springboot 提供的 SpringApplicationRunListener 的实现类 EventPublishingRunListener,所有对 SpringApplicationRunListeners 的调用请求都会被传递给集合中的每一个 SpringApplicationRunListener

  4. EventPublishingRunListener 中持有一个事件发布器 SimpleApplicationEventMulticaster,在 EventPublishingRunListener 的构造函数中,会将 SimpleApplicationEventMulticaster 创建出来并将 SpringApplication 中的所有 ApplicationListener 设置给 SimpleApplicationEventMulticaster。当 EventPublishingRunListener starting()environmentPrepared() 等方法被调用时,EventPublishingRunListener 会创建对应的事件(ApplicationStartingEventApplicationEnvironmentPreparedEvent)并通过 SimpleApplicationEventMulticaster 向适合接收当前事件的 ApplicationListener 发布;

  5. SimpleApplicationEventMulticaster 发布事件时,会先获取出所有适合接收当前事件的 ApplicationListener,然后调用这些 ApplicationListener onApplicationEvent() 方法,每一个 ApplicationListener 会在其实现的 onApplicationEvent() 方法中完成对事件的处理。

图示如下。


四. 外部化配置加载

Springboot 启动时,会在调用运行时监听器的 starting() 方法后创建 DefaultApplicationArguments 对象,然后就会开始加载外部化配置。

外部化配置通常由 application.yml 文件(或者 application.properties 文件)提供,在 application.yml 文件中添加配置项也是最常用的外部化配置方式。实际上为 Springboot 应用程序添加外部化配置的方式还有许多种,可以参考 Springboot-外部化配置,下面是较为常用的外部化配置方式的优先级(由上到下优先级逐渐降低)。

  1. 命令行参数,即 Command line arguments

  2. JAVA 系统属性,即 Java System propertiesSystem#getProperties);

  3. 操作系统环境变量,即 OS environment variables

  4. 配置数据文件(例如 application.yml 文件),即 Config datasuch as application.properties files) jar 包外指定了 profile 的配置数据文件:application-{profile}.yml jar 包外的配置数据文件:application.yml jar 包内指定了 profile 的配置数据文件:application-{profile}.yml jar 包内的配置数据文件:application.yml

  5. 作用在由 @Configuration 注解修饰的类上的 @PropertySource 注解,即 @PropertySource annotations on your @Configuration classes

  6. 默认属性,即 Default propertiesspecified by setting SpringApplication#setDefaultProperties)。

Springboot 在启动过程中的 SpringApplication#prepareEnvironment 方法中会加载上述的外部化配置为 EnvironmentEnvironment Springboot 外部化配置的入口,通过 Environment 可以获取到 Springboot 加载的所有外部化配置。

下图给出了 SpringApplication#prepareEnvironment 方法执行完后 Environment 的详细信息。


可见 Environment 的实际类型为 StandardServletEnvironment,这是和 Springboot 的应用程序类型挂钩,这点后面再说。StandardServletEnvironment 内部持有一个 MutablePropertySources 对象,该对象持有一个 PropertySource 的集合,Springboot 加载的每一种外部化配置都会最终被解析为一个 PropertySource 的实现类并存放在 MutablePropertySources PropertySource 集合中,PropertySource 就是每一种外部化配置源在 Springboot 中的体现,其提供了对外部化配置的各种操作。根据上图为例,给出一部分外部化配置源与 PropertySource 的实现类的对应关系。

启动程序时通过命令行指定的应用程序参数(args)会被先创建为 DefaultApplicationArguments 对象,然后再被解析为 SimpleCommandLinePropertySource,例如通过 IDEA 进行如下配置。


那么对应的 SimpleCommandLinePropertySource 如下所示。


如果在 resources 目录创建一个 application.yml 文件,且内容如下。

server:  port: 8080  address: 127.0.0.1
复制代码

那么对应的 OriginTrackedMapPropertySource 如下所示。


下面将从 SpringApplication#prepareEnvironment 方法为入口,对 Springboot 启动流程中的外部化配置加载进行简要分析。SpringApplication#prepareEnvironment 方法如下所示。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {    // 创建ConfigurableEnvironment对象    ConfigurableEnvironment environment = getOrCreateEnvironment();    // 将命令行参数解析为PropertySource并加载到Environment中    configureEnvironment(environment, applicationArguments.getSourceArgs());    ConfigurationPropertySources.attach(environment);    // 发布Environment准备好的事件    // 进一步加载更多的外部化配置到Environment中    listeners.environmentPrepared(bootstrapContext, environment);    DefaultPropertiesPropertySource.moveToEnd(environment);    configureAdditionalProfiles(environment);    bindToSpringApplication(environment);    if (!this.isCustomEnvironment) {        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,                deduceEnvironmentClass());    }    ConfigurationPropertySources.attach(environment);    return environment;}
复制代码

SpringApplication#prepareEnvironment 方法中,首先会调用 getOrCreateEnvironment() 方法创建 ConfigurableEnvironment 对象,创建出来的 ConfigurableEnvironment 的实际类型会根据 SpringApplication 初始化时推断出来的 WEB 应用程序类型而定,如果 WEB 应用程序类型为 SERVLET,则创建出来的 ConfigurableEnvironment 实际类型为 StandardServletEnvironment,并且在初始化 StandardServletEnvironment 时还会一并将 JAVA 系统属性和操作系统环境变量这两个外部化配置加载到 StandardServletEnvironment 中。

在创建好 StandardServletEnvironment 后,会再将命令行参数解析为 PropertySource 并加载到 StandardServletEnvironment 中,随后就通过 Springboot 事件机制向 ApplicationListener 发布 Environment 准备好的事件,这里会接收该事件的 ApplicationListener EnvironmentPostProcessorApplicationListener2.4.0 版本以前为 ConfigFileApplicationListener,该监听器从 2.4.0 版本起被废弃)。

接下来先分析一下 getOrCreateEnvironment() 方法的实现。

private ConfigurableEnvironment getOrCreateEnvironment() {    if (this.environment != null) {        return this.environment;    }    // 根据WEB应用程序类型创建不同的ConfigurableEnvironment    switch (this.webApplicationType) {    case SERVLET:        return new StandardServletEnvironment();    case REACTIVE:        return new StandardReactiveWebEnvironment();    default:        return new StandardEnvironment();    }}
复制代码

StandardServletEnvironment 的类图如下所示。


StandardServletEnvironment 在初始化时会先调用到其父类 AbstractEnvironment 的构造方法,如下所示。

public AbstractEnvironment() {    customizePropertySources(this.propertySources);}
复制代码

实际会调用到 StandardServletEnvironment 实现的 customizePropertySources() 方法,如下所示。

protected void customizePropertySources(MutablePropertySources propertySources) {    // Servlet相关的外部化配置的加载    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {        propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));    }    // 调用父类StandardEnvironment实现的customizePropertySources()方法    super.customizePropertySources(propertySources);}
复制代码

继续看 StandardEnvironment 实现的 customizePropertySources() 方法,如下所示。

protected void customizePropertySources(MutablePropertySources propertySources) {    // 将JAVA系统属性解析为PropertiesPropertySource,并加载到PropertySource集合中    propertySources.addLast(            new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));    // 将操作系统环境变量解析为SystemEnvironmentPropertySource,并加载到PropertySource集合中    propertySources.addLast(            new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));}
复制代码

到这里 getOrCreateEnvironment() 方法做的事情分析完毕。

下面再分析一下 EnvironmentPostProcessorApplicationListener 接收到 Environment 准备好的事件(ApplicationEnvironmentPreparedEvent)后的执行流程,EnvironmentPostProcessorApplicationListener onApplicationEvent() 方法如下所示。

public void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {        // 事件event的实际类型为ApplicationEnvironmentPreparedEvent        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);    }    if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent((ApplicationPreparedEvent) event);    }    if (event instanceof ApplicationFailedEvent) {        onApplicationFailedEvent((ApplicationFailedEvent) event);    }}
复制代码

继续看

onApplicationEnvironmentPreparedEvent() 方法。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {    ConfigurableEnvironment environment = event.getEnvironment();    SpringApplication application = event.getSpringApplication();    // 遍历所有EnvironmentPostProcessor的实现类,每个EnvironmentPostProcessor的实现类都会对相应的外部化配置做后置处理    // 处理配置数据文件的EnvironmentPostProcessor的实际类型为ConfigDataEnvironmentPostProcessor    for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {        postProcessor.postProcessEnvironment(environment, application);    }}
复制代码

EnvironmentPostProcessor 的继承树如下所示。


每一个 EnvironmentPostProcessor 的实现类都会对相应的外部化配置做后置处理,例如 RandomValuePropertySourceEnvironmentPostProcessor 会加载一个 RandomValuePropertySource Environment 中,SystemEnvironmentPropertySourceEnvironmentPostProcessor 会将 Environment 中的 SystemEnvironmentPropertySource 替换为 SystemEnvironmentPropertySource 的子类 OriginAwareSystemEnvironmentPropertySource

EnvironmentPostProcessor 的实现类中,有一个较为重要的实现类叫做 ConfigDataEnvironmentPostProcessor,其可以将配置数据文件(application.yml 等)加载为 OriginTrackedMapPropertySource 并设置到 Environment 中。

至此,Springboot 启动流程中的外部化配置加载分析完毕,下面是小结。

  1. 首先会根据初始化 SpringApplication 时推断出来的 WEB 应用程序类型创建不同的 Environment,例如 WEB 应用程序类型为 SERVLET 时,创建的 Environment 的实际类型为 StandardServletEnvironment

  2. 在创建 StandardServletEnvironment 时,就会向 StandardServletEnvironment 中加载一部分外部化配置,在这个阶段加载的外部化配置主要是 JAVA 系统属性和操作系统环境变量;

  3. 在创建 StandardServletEnvironment 后,还会通过 Springboot 事件机制向 EnvironmentPostProcessorApplicationListener 发布 ApplicationEnvironmentPreparedEvent 事件,EnvironmentPostProcessorApplicationListener 中收到 ApplicationEnvironmentPreparedEvent 事件后,会调用 EnvironmentPostProcessor 的实现类完成对 Environment 的后置处理,即会继续向 Environment 加载外部化配置,配置数据文件(application.yml 等)的加载就在这个阶段完成;

  4. StandardServletEnvironment 内部持有一个 MutablePropertySources 对象,该对象持有一个 PropertySource 的集合,Springboot 加载的每一种外部化配置都会最终被解析为一个 PropertySource 的实现类并存放在 MutablePropertySources PropertySource 集合中,PropertySource 就是每一种外部化配置源在 Springboot 中的体现,其提供了对外部化配置的各种操作。

总结

Springboot 启动时,第一件重要事件就是初始化 SpringApplication,并主要完成如下事情。

  1. 设置源。实际就是设置 Spring 容器启动时依赖的初始配置类,也就是 Springboot 中的启动类;

  2. 设置 WEB 应用程序类型。例如可以是 SERVLETREACTIVE 等;

  3. 加载并设置 BootstrapperApplicationContextInitializer ApplicationListener

  4. 设置应用程序主类的 Class 对象。

然后 Springboot 启动时还会开启事件机制,主要就是通过运行时监听器 EventPublishingRunListener 创建事件并分发给对应的 ApplicationListener

再然后会加载外部化配置,也就是得到很重要的 Environment 对象,通过 Environment 对象就可以拿到 Springboot 加载的所有外部化配置。

再然后会完成容器刷新,也就是执行 Spring 中的各种扩展点,初始化各种 bean,这部分逻辑属于是 Spring 的逻辑,故本文并未详细介绍。除此之外,在容器刷新时,还会完成 WEB 容器的启动,例如启动 Springboot 内嵌的 Tomcat,这部分内容比较多,会在后面单独进行分析。

最后,Springboot 在整个启动流程中,会借助事件机制来发布各种事件,发布事件就是借助于上述提到的 EventPublishingRunListener,这是一个运行时监听器,是 Springboot 中提供的监听器,不要和 Spring 中的 ApplicationListener 混淆了。

用户头像

Java你猿哥

关注

一只在编程路上渐行渐远的程序猿 2023-03-09 加入

关注我,了解更多Java、架构、Spring等知识

评论

发布
暂无评论
详细分析Spring Boot启动流程_spring_Java你猿哥_InfoQ写作社区