Rubin's Blog

  • 首页
  • 关于作者
  • 隐私政策
享受恬静与美好~~~
分享生活的点点滴滴~~~
  1. 首页
  2. SpringBoot
  3. 正文

SpringBoot之启动流程解析

2021年 9月 1日 820点热度 0人点赞 0条评论

前言

经历过java面试的小伙伴,大部分都会被问到过SpringBoot启动流程类型的问题。这作为我们深入了解SpringBoot框架来说,是一门必修课。本博文会在源码的基础上记录一下SpringBoot的启动流程。

SpringBoot的启动流程

我们点进SpringApplication.run(Class[] primarySources, String[] args)这个方法会发现,该方法分为两个部分:初始化对象和执行该对象的run方法,如图所示:

下面呢,我们分两个阶段来讲解启动流程。

初始化阶段

我们点进SpringBootApplication的构造器,如图所示:

这里,我们需要重点关注方法:getSpringFactoriesInstances。该方法会在启动过程中多次用到,我们看一下方法体,如下图所示:

总结一下,在类的初始化阶段,主要是做的动作主要有:设置启动类型(分为Application项目、Web项目和Reactive项目)、设置容器的初始化器、设置容器的监听器和设置容器的启动类。这里需要注意的一点是:我们的项目配置文件是在监听器初始化过程中加载的,也就是说容器初始化完成,我们项目的配置文件已经加载完毕了。

启动阶段

启动阶段步骤较多,我把源码贴在下面:

/**
  * Run the Spring application, creating and refreshing a new
  * {@link ApplicationContext}.
  * @param args the application arguments (usually passed from a Java main method)
  * @return a running {@link ApplicationContext}
  */
public ConfigurableApplicationContext run(String... args) {
    // 初始化一个计时器并开启
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置headless环境变量
    configureHeadlessProperty();
    // 封装所有的监听器并全部启动
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 封装启动参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 配置我们的环境上下文
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 配置需要忽略的bean信息
        configureIgnoreBeanInfo(environment);
        // 打印Banner
        Banner printedBanner = printBanner(environment);
        // 创建应用上下文
        context = createApplicationContext();
        // 获取异常报告器实例列表
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新应用上下文
        refreshContext(context);
        // 刷新应用上下文后置函数 预留函数(钩子函数)
        afterRefresh(context, applicationArguments);
        // 停止计时器
        stopWatch.stop();
        // 打印启动时间
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 触发监听器的容器初始化完成动作
        listeners.started(context);
        // 触发自定义的ApplicationRunner或者CommandLineRunner的实现类的组件列表
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

上文的代码,我已经将每一步都加了说明文案。其实步骤比较清晰,下面我们针对比较重要的步骤来做进一步的讲解。

首先,我们看一下容器环境的准备步骤,如下图所示:

这里的话,比较重要的就是根据环境创建环境对象。这里跟容器初始化的判断逻辑一致,就是判断当前环境是一个普通的应用环境还是Web环境。

环境准备就绪之后,我们再来关注一下我们应用上下文的创建步骤。如下图所示:

这里我们可以看到很熟悉的逻辑,根据当前环境来选择反射创建不同的上下文实例。由于我们目前来讲Web环境应用的比较多,我们就按照Web环境来走一下接下来的步骤。Web环境下创建的上下文示例为org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext。

创建好实例之后,我们看一下准备阶段的步骤:

准备好容器之后,就进入了我们最重要的容器刷新阶段:

我们点进refresh方法:

这里我们可以看到,调用的是AbstractApplicationContext的refresh方法,该方法是我们学习Spring家族框架时最重要也是最核心的方法。我们点开该方法:

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 准备刷新,主要是设置刷新标识,校验必要参数等等准备工作
        prepareRefresh();

        // 获取内部容器实例,该方法包含刷新内容容器并返回两个步骤,均为抽象方法
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 准备内部容器,主要是注册一写初始化的bean以及设置各种属性
        prepareBeanFactory(beanFactory);

        try {
            // 向上下文中添加了一系列的Bean的后置处理器
            postProcessBeanFactory(beanFactory);

            // 实例化并调用所有的BeanFactoryPostProcessor,此步骤很重要,我们的自动装配就在此步骤内实现的
            invokeBeanFactoryPostProcessors(beanFactory);

            // 注册所有的Bean的后置处理器
            registerBeanPostProcessors(beanFactory);

            // 初始化信息源
            initMessageSource();

            // 初始化容器时间广播器
            initApplicationEventMulticaster();

            // 钩子函数
            onRefresh();

            // 注册监听器
            registerListeners();

            // 实例化所有的非懒加载的bean
            finishBeanFactoryInitialization(beanFactory);

            // 刷新完成函数
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

其中,invokeBeanFactoryPostProcessors函数是重点。该函数会实例化所有的 BeanFactoryPostProcessor并调用其postProcessBeanFactory方法来向容器中自定义注册一些bean的定义信息。这里就会调用到一个非常重要的后置处理器ConfigurationClassPostProcessor。这个后置处理器会处理所有的带有@Configuration注解的类并解析其中自定义的@Bean注解注入的bean。除此之外,他还会查看是否该类上标有@ComponentScan注解,有的话会扫描基础路径下所有的带有@Component(以及子注解)的bean。最最重要的是,他还会扫描该类上的@Import注解扫描对应类的方法来注册自定义的bean(也就是我们讲的自动装配的原理,这里就会加载定义的自动配置的类并筛选排序后进行注入容器)。

后面就是一些后续流程,有兴趣的可以继续研究一下。

总结

我们总结一下,SpringBoot的启动流程分为两个阶段:初始化以及运行阶段。初始化阶段主要是设置一些容器初始化起和监听器以及一些属性,运行阶段主要是初始化并配置环境、初始化容器、配置并刷新容器和发布一系列的事件。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: SpringBoot
最后更新:2022年 6月 9日

RubinChu

一个快乐的小逗比~~~

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • 前言
  • SpringBoot的启动流程
    • 初始化阶段
    • 启动阶段
  • 总结
最新 热点 随机
最新 热点 随机
问题记录之Chrome设置屏蔽Https禁止调用Http行为 问题记录之Mac设置软链接 问题记录之JDK8连接MySQL数据库失败 面试系列之自我介绍 面试总结 算法思维
Dubbo之配置项说明 MySQL学习之JSON类型 SpringCloud之Gateway网关组件 SpringCloud Alibaba之Sentinel SSM整合 MySQL学习之时间类型

COPYRIGHT © 2021 rubinchu.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

京ICP备19039146号-1