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

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 深入理解 SpringBoot 启动机制——run()启动源码全过程分析

深入理解 SpringBoot 启动机制:run()启动源码全过程分析
作者:MyBug juejin.im/post/5d3eddb4f265da03b94fcca5

一、前言

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

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

    推断当前 WEB 应用类型

    设置监听器

    如果,各位同学有遗忘的,可以去复习一下上篇文章。

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

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

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

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

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

    
    public ConfigurableApplicationContext run(String... args) {
            // 1、创建并启动计时监控类
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
    
            // 2、初始化应用上下文和异常报告集合
            ConfigurableApplicationContext context = null;
            CollectionSpringBootExceptionReporter 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();
        }
    

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

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

    
    ConfigurableApplicationContext context = null;
    CollectionSpringBootExceptionReporter 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.class, String[].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 加载所有资源
            SetObject 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的路上提供一点帮助。

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

    END

    Java面试题专栏

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

    欢迎长按下图关注公众号后端技术精选

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

    原文始发于微信公众号(后端技术精选):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 深入理解 SpringBoot 启动机制——run()启动源码全过程分析


     上一篇
    深入理解 SpringBoot 启动机制——初始化流程(run方法) 深入理解 SpringBoot 启动机制——初始化流程(run方法)
    点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:MuBug juejin.im/post/5d32fca5518825477162ab5e 一、前言在上一篇,我们主要介绍了,注解@Springbo
    下一篇 
    使用 JWT 来保护你的 SpringBoot 应用 使用 JWT 来保护你的 SpringBoot 应用
    作者:freewolf juejin.im/post/5902ca705c497d005829ed6f 关键词Spring Boot、OAuth 2.0、JWT、Spring Security、SSO、UAA 写在前面这