深入理解 SpringBoot 启动机制:run()启动源码全过程分析

一、前言

上一篇我们了解到 new SpringApplication(primarySources)实例初始化源码的加载过程,通过走跟源码分析了基本初始化过程如下:

  1. 资源初始化资源加载器为 null
  2. 断言主要加载资源类不能为 null,否则报错
  3. 初始化主要加载资源类集合并去重
  4. 推断当前 WEB 应用类型
  5. 设置应用上下文初始化器
  6. 设置监听器
  7. 推断主入口应用类

如果,各位同学有遗忘的,可以去复习一下上篇文章深入理解SpringBoot核心原理:初始化流程(run方法)

那么,这篇我们继续往下面分析其核心 run 方法。

二、SpringApplication 实例 run 方法运行过程

深入理解 SpringBoot 启动机制:run()启动源码全过程分析

下面继续来分析SpringApplication对象的run方法实现过程以及运行原理。

还是跟之前的分析流程一样,先来看一下run方法里面总体的流程实现:

public ConfigurableApplicationContext run(String... args) {
        // 1、创建并启动计时监控类
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 2、初始化应用上下文和异常报告集合
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

        // 3、设置系统属性 `java.awt.headless` 的值,默认值为:true
        configureHeadlessProperty();

        // 4、创建所有 Spring 运行监听器并发布应用启动事件
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {

            // 5、初始化默认应用参数类
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            // 6、根据运行监听器和应用参数来准备 Spring 环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);

            // 7、创建 Banner 打印类
            Banner printedBanner = printBanner(environment);

            // 8、创建应用上下文
            context = createApplicationContext();

            // 9、准备异常报告器
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);

            // 10、准备应用上下文
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            // 11、刷新应用上下文
            refreshContext(context);

            // 12、应用上下文刷新后置处理
            afterRefresh(context, applicationArguments);

             // 13、停止计时监控类
            stopWatch.stop();

            // 14、输出日志记录执行主类名、时间信息
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }

            // 15、发布应用上下文启动完成事件
            listeners.started(context);

            // 16、执行所有 Runner 运行器
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {

            // 17、发布应用上下文就绪事件
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        // 18、返回应用上下文
        return context;
    }

三、run 方法运行过程分解

3.1 创建并启动计时监控类

StopWatch stopWatch = new StopWatch();
stopWatch.start();

进入start()方法如下:

    /**
     * Start an unnamed task. The results are undefined if {@link #stop()}
     * or timing methods are called without invoking this method.
     * @see #stop()
     */

    public void start() throws IllegalStateException {
        start("");
    }

    /**
     * Start a named task. The results are undefined if {@link #stop()}
     * or timing methods are called without invoking this method.
     * @param taskName the name of the task to start
     * @see #stop()
     */

    public void start(String taskName) throws IllegalStateException {
        if (this.currentTaskName != null) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        }
        this.currentTaskName = taskName;
        this.startTimeMillis = System.currentTimeMillis();
    }

首先记录了当前任务的名称,默认为空字符串,然后记录当前 Spring Boot 应用启动的开始时间。

3.2 初始化应用上下文和异常报告集合

ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

3.3 设置系统属性 java.awt.headless 的值

configureHeadlessProperty();

至于为什么设置这个属性值为true,可以参考下面这篇文章:

https://www.cnblogs.com/princessd8251/p/4000016.html

3.4 创建所有 Spring 运行监听器并发布应用启动事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

进去看一下创建spring运行监听器的相关源码:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.classString[].class };
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
SpringApplicationRunListeners {
        ......
        SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }
        ......
}

创建逻辑和之前实例化初始化器和监听器的一样,一样调用的是getSpringFactoriesInstances 方法来获取配置的监听器名称并实例化所有的类。

SpringApplicationRunListener所有监听器配置在 spring-boot-2.0.4.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener

3.5 初始化默认应用参数类

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

3.6 根据运行监听器和应用参数来准备 Spring 环境

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);

下面我们主要来看下准备环境的 prepareEnvironment 源码:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) 
{
        // 1.Create the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 2.Configure the environment
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

3.7 创建 Banner 打印类

Banner printedBanner = printBanner(environment);

3.8 创建应用上下文

context = createApplicationContext();

进去源码,可以知道这里主要是根据不同的应用类型初始化不同的上下文应用类。

3.9 准备异常报告器

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

getSpringFactoriesInstances ------>>createSpringFactoriesInstances ------->>>逻辑和之前实例化初始化器和监听器的一样,一样调用的是 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。

该异常报告处理类配置在 spring-boot-2.0.4.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面。

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=
org.springframework.boot.diagnostics.FailureAnalyzers

3.10 准备应用上下文

prepareContext(context, environment, listeners, applicationArguments, printedBanner);

接下来进入prepareContext方法:

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        // 配置上下文的 bean 生成器及资源加载器
        postProcessApplicationContext(context);
        // 为上下文应用所有初始化器
        applyInitializers(context);
        // 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
        listeners.contextPrepared(context);
        // 记录日志
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans 启动两个特殊的单例bean
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources 加载所有资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        // 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);
    }

3.11 刷新应用上下文

refreshContext(context);

3.12 应用上下文刷新后,自定义处理

afterRefresh(context, applicationArguments);

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {

}

3.13 停止计时监控类

stopWatch.stop();

计时监听器停止,并统计一些任务执行信息。

3.14 输出日志记录执行主类名、时间信息

if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);

}

3.15 发布应用上下文启动完成事件

listeners.started(context);

这里会触发所有 SpringApplicationRunListener 监听器的 started 事件方法。

3.16 执行所有 Runner 运行器

callRunners(context, applicationArguments);

执行所有ApplicationRunner以及CommandLineRunner执行器

3.17 发布应用上下文就绪事件

listeners.running(context);

触发所有 SpringApplicationRunListener 监听器的 running 事件方法。

3.18 返回应用上下文

return context;

四、总结

关于SpringBootApplication.run()启动实例初始化以及实例加载run方法的源码分析到此结束,分析源码是件有点痛苦的事情,不过分析完源码后,你会对SpringBoot是如何加载以及初始化有更全面的了解。

当然其中也有其它的一些东西值得学习,比如Spring事件监听,如何使用单例,自动化配置等等,最后,希望给各位同学在学习SpringBoot的路上提供一点帮助。

看完,如果觉得有收获,希望点个赞。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们