laitimes

Analyze the Spring Boot startup process in detail

author:Java Cub
Analyze the Spring Boot startup process in detail

The intern sister in the group talked about a Springboot launch.

Talking about the life cycle of Spring's bean,

I thought to myself,

This nizi probably confuses the logic of Springboot and Spring.

This has to be treated for her.

preface

This article provides a detailed analysis of the Springboot startup process. However, please note that the Springboot startup process is the logic of Springboot, please do not mix the logic related to the Springboot process with the logic related to Spring, such as mixing the logic of Spring's bean life cycle with the Springboot startup process, then the whole system is complex and confusing.

Therefore, this article only focuses on the Springboot startup process, and the part involving Spring will be briefly explained and skipped.

A structural diagram of the whole is as follows.

Analyze the Spring Boot startup process in detail

Springboot version: 2.4.1

body

1. Springboot startup flow chart and description

The following is a startup flowchart of Springboot.

Analyze the Spring Boot startup process in detail

After the SpringApplication is initialized, the run() method of the SpringApplication object is called, which is the entry point for the Springboot startup and corresponds to the start in the whole flowchart. The run() method description of the SpringApplication object is given below, as shown below.

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;
}           

Initialization of the SpringApplication

In general, the startup class of a Springboot application is defined as follows.

@SpringBootApplication
public class LearnStartApplication {

    public static void main(String[] args) {
        SpringApplication.run(LearnStartApplication.class, args);
    }

}           

Following along with the static run() method of SpringApplication, you will find the following implementation.

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);
}           

That is, when Springboot starts, it will first create a SpringApplication, and then complete the startup through the run() method of the SpringApplication. So let's analyze the initialization logic of SpringApplication, and its construction method is as follows.

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();
}           

To sort out the SpringApplication constructor, the following things are done.

  1. Set the source. Usually, the source in Springboot is the startup class of Springboot;
  2. SET THE WEB APPLICATION TYPE. Inferring the type of current web application by determining whether certain classes exist under the classpath;
  3. Load and set up Bootstrapper, ApplicationContextInitializer, and ApplicationListener. Use the SpringFactoriesLoader to complete the loading of Bootstrapper, ApplicationContextInitializer and ApplicationListener based on the SPI mechanism, and then set it into SpringApplication;
  4. Sets the Class object of the application's main class.

The above things are analyzed below.

1. Set the source

The source here is the initial configuration class that the Spring container depends on when it starts, and in Springboot, the initial configuration class is usually the startup class. Let's take a look at the value of the primarySources field by debugging, as shown below.

Analyze the Spring Boot startup process in detail

The visible source is the Class object of the Springboot startup class.

2. SET THE WEB APPLICATION TYPE

The WebApplicationType#deduceFromClasspath method is shown below.

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;
}           

The fully qualified names of several classes for judgment are predefined in WebApplicationType, and then ClassUtils is used in the deduceFromClasspath() method to determine whether the predefined classes exist, so that the current web application type can be inferred. In the example project, if only the spring-boot-starter package is introduced, the inferred WebApplicationType is NONE, as shown below.

Analyze the Spring Boot startup process in detail

If the spring-boot-starter-web package is introduced, the inferred WebApplicationType is a SERVLET, as shown below.

Analyze the Spring Boot startup process in detail

Load and set up Bootstrapper, ApplicationContextInitializer, and ApplicationListener

Here's an analysis of how to load Bootstrapper, ApplicationContextInitializer, and ApplicationListener. They are loaded using the getSpringFactoriesInstances() method, so let's take a look at the implementation.

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;
}           

The main thing is to complete the loading based on SpringFactoriesLoader, the loading mechanism is the same as the automatic assembly in Springboot, the only difference is that in the automatic assembly in the spring.factories file, the fully qualified name of the @EnableAutoConfiguration is used as the key to obtain the fully qualified name collection, and here it is according to Bootstrapper. ApplicationContextInitializer and ApplicationListener's fully qualified names are used as keys to get the fully qualified name collection, using the spring.factories file in the spring-boot-autoconfigure package as an example, as described below.

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. Set the Class object of the application's main class

The SpringApplication#deduceMainApplicationClass method that gets the Class object of the application's main class is shown below.

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;
}           

Getting the Class object of the main class of the application is implemented through the stack, and the debugging screenshot is given below.

Analyze the Spring Boot startup process in detail

Springboot event mechanism

At the beginning of Springboot startup, there is a step of logic to get the runtime listener and eventually get a SpringApplicationRunListeners object, let's look at the implementation of the getRunListeners() method of getting the runtime listener.

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;
}           

In the getRunListeners() method, the implementation class of the SpringApplicationRunListener interface is first obtained based on the SPI mechanism of SpringFactoriesLoader, and an implementation class of the SpringApplicationRunListener interface is provided in the spring-boot package. For EventPublishingRunListener, which is also the only built-in runtime listener provided by Springboot, the SpringApplicationRunListeners object obtained through the getRunListeners() method holds a collection of SpringApplicationRunListeners. This collection must contain an EventPublishingRunListener object by default.

Let's take the starting() method of SpringApplicationRunListeners as an example to analyze how SpringApplicationRunListeners work.

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();
}           

Combining the starting() and doWithListeners() methods of SpringApplicationRunListeners, it can be seen that SpringApplicationRunListeners will pass the call of the starting() method to each runtime listener it holds. So SpringApplicationRunListeners is an application for composition patterns.

The event mechanism in Springboot should logically be implemented by EventPublishingRunListener, the only runtime listener provided by Springboot. The following analyzes the logic of EventPublishingRunListener, or takes the starting() method of EventPublishingRunListener as an example to illustrate.

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

The EventPublishingRunListener's starting() method creates an ApplicationStartingEvent object first, and then publishes the event through a SimpleApplicationEventMulticaster object held by the EventPublishingRunListener.

So let's continue to analyze how SimpleApplicationEventMulticaster publishes events and to whom, SimpleApplicationEventMulticaster's multicastEvent() method is shown below.

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);
        }
    }
}           

The MulticastEvent() method of SimpleApplicationEventMulticaster will first fetch the ApplicationListener loaded when the SpringApplication is initialized, and then iterate through the ApplicationListener suitable for receiving the current event. Then publish events to the ApplicationListener asynchronously or synchronously, continue to see the invokeListener() method, as shown below.

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;
        }
    }
}           

The invokeListener() method of SimpleApplicationEventMulticaster actually calls the doInvokeListener() method, and the onApplicationEvent() method of the ApplicationListener is called in the doInvokeListener() method. So here we call the logic of the ApplicationListener to actually handle the event.

Now for a summary of the event listening mechanism in Springboot.

  1. When the SpringApplication is initialized, all ApplicationListeners are loaded;
  2. At the beginning of Springboot startup, the SpringApplication#getRunListeners method is called to create a SpringApplicationRunListeners object.
  3. SpringApplicationRunListeners is a compositional application, which holds a collection of SpringApplicationRunListeners, and by default there will be an implementation class of SpringApplicationRunListener provided by Springboot, EventPublishingRunListener. All requests for SpringApplicationRunListeners are passed to each SpringApplicationRunListener in the collection;
  4. EventPublishingRunListener holds an event publisher, SimpleApplicationEventMulticaster, in the constructor of EventPublishingRunListener. The SimpleApplicationEventMulticaster is created and all ApplicationListeners in the SpringApplication are set to the SimpleApplicationEventMulticaster. When EventPublishingRunListener's starting(), environmentPrepared() and other methods are called, EventPublishingRunListener creates corresponding events (ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent) and publish through SimpleApplicationEventMulticaster to ApplicationListeners suitable for receiving current events;
  5. When SimpleApplicationEventMulticaster publishes an event, it first gets all the ApplicationListeners that are suitable for receiving the current event, and then calls the onApplicationEvent() methods of these ApplicationListeners. Each ApplicationListener completes its handling of events in its implementation of the onApplicationEvent() method.

The illustration is shown below.

Analyze the Spring Boot startup process in detail

IV. Externalize configuration loading

When Springboot starts, it creates a DefaultApplicationArguments object after calling the starting() method of the runtime listener, and then it starts loading the externalized configuration.

Externalized configuration is usually provided by the application.yml file (or application.properties file), and adding configuration items to the application.yml file is also the most common way to externalize the configuration. In fact, there are many ways to add externalization configuration to Springboot applications, you can refer to Springboot - Externalization Configuration, the following is the priority of the more commonly used externalization configuration methods (from top to bottom priority gradually decreased).

  1. Command line arguments, i.e. Command line arguments;
  2. JAVA system properties, i.e. Java System properties(System#getProperties);
  3. OS environment variables, i.e. OS environment variables;
  4. Configuration data files (e.g. application.yml file), i.e. Config data (such as application.properties files) The configuration data file outside the jar package specifies the profile data file: application-{profile}.yml The configuration data file outside the jar package: application.yml The configuration data file of the profile is specified in the jar package: application-{profile}.yml The configuration data file in the jar package: application.yml
  5. @PropertySource annotations acting on classes decorated by @Configuration annotations, i.e. @PropertySource annotations on your @Configuration classes;
  6. Default properties, i.e. Default properties (specified by setting SpringApplication#setDefaultProperties).

Springboot will load the above externalized configuration in the SpringApplication#prepareEnvironment method during the startup process, Environment is the entry of Springboot externalized configuration, through the Environment can get all the externalized configurations loaded by Springboot.

The following figure shows the details of the Environment after the SpringApplication#prepareEnvironment method has been executed.

Analyze the Spring Boot startup process in detail

It can be seen that the actual type of Environment is StandardServletEnvironment, which is linked to Springboot's application type, which will be discussed later. StandardServletEnvironment internally holds a MutablePropertySources object, which holds a collection of PropertySource. Each externalized configuration loaded by Springboot is eventually parsed into an implementation class of a PropertySource and stored in the PropertySource collection of MutablePropertySources, which is the embodiment of each externalized configuration source in Springboot and provides various operations on the externalized configuration. According to the above figure as an example, the correspondence between some externalized configuration sources and the implementation class of PropertySource is given.

Externalize the configuration PropertySource
Command line arguments SimpleCommandLinePropertySource
JAVA SYSTEM PROPERTIES PropertiesPropertySource
Operating system environment variables OriginAwareSystemEnvironmentPropertySource
Configure the data file OriginTrackedMapPropertySource

The application parameters (args) specified from the command line when starting the program are first created as DefaultApplicationArguments objects, and then parsed into SimpleCommandLinePropertySource, for example through IDEA.

Analyze the Spring Boot startup process in detail

Then the corresponding SimpleCommandLinePropertySource is shown below.

Analyze the Spring Boot startup process in detail

If you create an application.yml file in the resources directory, the contents are as follows.

server:
  port: 8080
  address: 127.0.0.1           

Then the corresponding OriginTrackedMapPropertySource is shown below.

Analyze the Spring Boot startup process in detail

The following is a brief analysis of the externalized configuration loading in the Springboot startup process from the SpringApplication#prepareEnvironment method. The SpringApplication#prepareEnvironment method looks like this.

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;
}           

In the SpringApplication#prepareEnvironment method, the getOrCreateEnvironment() method is first called to create the ConfigurableEnvironment object. The actual type of the created ConfigurableEnvironment will be based on the type of the web application inferred when the SpringApplication is initialized, and if the type of the web application is SERVLET, the actual type of the created ConfigurableEnvironment is StandardServletEnvironment. When initializing the StandardServletEnvironment, two externalized configurations, Java system properties and operating system environment variables, are also loaded into the StandardServletEnvironment.

After creating the StandardServletEnvironment, the command-line arguments will be parsed to PropertySource and loaded into the StandardServletEnvironment, and then the Environment-prepared events will be released to the ApplicationListener through the Springboot event mechanism. The ApplicationListener that receives the event here is EnvironmentPostProcessorApplicationListener (previously ConfigFileApplicationListener prior to version 2.4.0, which has been deprecated since version 2.4.0).

Next, let's analyze the implementation of the getOrCreateEnvironment() method.

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();
    }
}           

The class diagram for StandardServletEnvironment is shown below.

Analyze the Spring Boot startup process in detail

When the StandardServletEnvironment is initialized, it first calls the constructor of its parent class, the AbstractEnvironment, as shown below.

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}           

The customizePropertySources() method implemented by the StandardServletEnvironment is actually called, as shown below.

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);
}           

Continue looking at the customizePropertySources() method implemented by StandardEnvironment, as shown below.

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()));
}           

At this point, what the getOrCreateEnvironment() method does is analyzed.

Let's analyze the execution process of EnvironmentPostProcessorApplicationListener after receiving the EnvironmentPrepared Event (ApplicationEnvironmentPreparedEvent). The onApplicationEvent() method of the EnvironmentPostProcessorApplicationListener is shown below.

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);
    }
}           

Continue to the onApplicationEnvironmentPreparedEvent() method.

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);
    }
}           

The inheritance tree of the EnvironmentPostProcessor is shown below.

Analyze the Spring Boot startup process in detail

Each implementation class of the EnvironmentPostProcessor will post-process the corresponding externalized configuration, for example, RandomValuePropertySourceEnvironmentPostProcessor will load a RandomValuePropertySource into the Environment. SystemEnvironmentPropertySourceEnvironmentPostProcessor replaces SystemEnvironmentPropertySource in Environment with OriginAwareSystemEnvironmentPropertySource, a subclass of SystemEnvironmentPropertySource 。

Among the implementation classes of EnvironmentPostProcessor, there is an important implementation class called ConfigDataEnvironmentPostProcessor, which can load the configuration data file (application.yml, etc.) as OriginTrackedMapPropertySource and set it to the Environment.

At this point, the externalized configuration loading analysis in the Springboot startup process has been completed, and the following is a summary.

  1. First, different environments will be created based on the type of web application inferred when initializing SpringApplication, for example, when the web application type is SERVLET, the actual type of the created environment is StandardServletEnvironment;
  2. When creating a StandardServletEnvironment, a part of the externalized configuration is loaded into the StandardServletEnvironment, and the externalized configuration loaded at this stage is mainly Java system properties and operating system environment variables;
  3. After the StandardServletEnvironment is created, the ApplicationEnvironmentPreparedEvent event is also published to the EnvironmentPostProcessorApplicationListener through the Springboot event mechanism. After receiving the ApplicationEnvironmentPreparedEvent event in the EnvironmentPostProcessorApplication Listener, it will call the implementation class of EnvironmentPostProcessor to complete the post-processing of the Environment, that is, it will continue to load the externalized configuration to the Environment. The loading of the configuration data file (application.yml, etc.) is done at this stage;
  4. StandardServletEnvironment internally holds a MutablePropertySources object, which holds a collection of PropertySource. Each externalized configuration loaded by Springboot is eventually parsed into an implementation class of a PropertySource and stored in the PropertySource collection of MutablePropertySources, which is the embodiment of each externalized configuration source in Springboot and provides various operations on the externalized configuration.

summary

When Springboot starts, the first important thing is to initialize the SpringApplication, and mainly do the following.

  1. Set the source. In fact, it is to set the initial configuration class that the Spring container depends on when starting, that is, the startup class in Springboot;
  2. SET THE WEB APPLICATION TYPE. FOR EXAMPLE, IT CAN BE SERVLET, REACTIVE, ETC.;
  3. Load and set up Bootstrapper, ApplicationContextInitializer, and ApplicationListener;
  4. Sets the Class object of the application's main class.

Then Springboot will also open the event mechanism when it starts, mainly through the runtime listener EventPublishingRunListener to create events and distribute them to the corresponding ApplicationListener.

Then the externalized configuration will be loaded, that is, the important Environment object will be obtained, and all the externalized configurations loaded by Springboot can be obtained through the Environment object.

Then the container refresh will be completed, that is, the execution of various extension points in Spring and initialization of various beans, which belongs to the logic of Spring, so this article does not introduce it in detail. In addition, when the container is refreshed, the startup of the WEB container will also be completed, such as starting Tomcat embedded in Springboot, which is more content, and will be analyzed separately later.

Finally, throughout the startup process, Springboot will publish various events with the help of the event mechanism, and the publishing event is with the help of the aforementioned EventPublishingRunListener, which is a runtime listener, which is a listener provided in Springboot, not to be confused with ApplicationListener in Spring.

If you think this article is helpful to you, please like it, and finally pay attention. It's not easy to create, thanks for your support!

Link: https://juejin.cn/post/7214831216028745783