前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 全家桶之 Spring Boot 2.6.4(九)- 启动流程解析

Spring 全家桶之 Spring Boot 2.6.4(九)- 启动流程解析

作者头像
RiemannHypothesis
发布2022-09-26 15:57:16
7700
发布2022-09-26 15:57:16
举报
文章被收录于专栏:Elixir

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

一、Debug Spring Boot 启动流程

创建工程spring-boot-fundamental,只添加基本依赖

Debug启动流程,在SpringApplication.run(AppApplication.class, args)这一行打上断点

创建SpringApplication对象

首先会创建SpringApplication对象

调用包含有ResourceLoader和Class<?>... 两个参数的构造函数创建对象

代码语言:javascript
复制
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

primarySources包含了主配置类,这句代码是判断传入的primarySources是否为空,如果主配置类存在就一定不为空,并将这些Class存储到LinkedHashSet集合中

代码语言:javascript
复制
this.webApplicationType = WebApplicationType.deduceFromClasspath();

判断当前应用是不是Web应用

debug到这个方法中去

判断当前应用是一个Web应用

代码语言:javascript
复制
this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

就是从spring.factories配置文件中获取所有的自动配置类

getSpringFactoriesInstances方法就是从配置文件中获取指定的配置类,根据传入的类型

代码语言:javascript
复制
this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

这句代码就是获取所有的BootstrapRegistryInitializer配置类,将其实例化并放入一个ArrayList中,赋值给bootstrapRegistryInitializers属性

代码语言:javascript
复制
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

这句代码就是设置initializers属性,通过getSpringFactoriesInstance获取配置文件中所有的ApplicationContextInitializer并实例化放入集合中作为initializers属性的值

Debug可以看到放置了这8个initializer到集合中

代码语言:javascript
复制
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

setListeners也是通过调用getSpringFactoriesInstance获取配置文件中所有的ApplicationListener类并实例化放入listeners属性的集合中 debug查看放入多少个listener

deduceMainApplicationClass()方法是决定哪个程序是主程序

再往下进行debug

确定了主程序

到此,整个SpringApplication对象创建完成了

创建SpringApplication大概有这几步:

  1. 保存主配置类
  2. 判断是否为Web应用
  3. 从类路径下找到META-INF/spring.factorues配置所有的ApplicationContextInitializer,然后保存起来

执行run()方法

在SpringApplication对象创建完成之后,开始执行run()方法;重新启动Debug,进入run方法

此时SpringApplication对象已经创建好,run方法中的流程就是Spring Boot启动的流程。

Step Over第一句289行代码只是为了标记一个起始时间

这里是将创建SpringApplication对象时从配置文件中获取所有的BootstrapRegistryInitializer配置类,这里将列表遍历,但是列表为空,返回一个默认的DefaultBootstrapContext

第291行声明了一个容器,接下来所有的代码就是往这个容器中注册组件,最终返回这个容器

context容器中注入了哪些组件? 292行配置headless属性为True,与awt应用相关

代码语言:javascript
复制
SpringApplicationRunListeners listeners = getRunListeners(args);

Step Into 进入到 getRunListeners方法

这里又调用了getSpringFactoriesInstances方法,这个方法的作用就是从类路径下的spring.factoies中获取指定的类,这里要获取的是所有的SpringApplicationRunListener类

这里获取了一个EventPublishRunListener

继续Step Over到294行

代码语言:javascript
复制
listeners.starting(bootstrapContext, this.mainApplicationClass);

这里调用了starting方法,参数为默认的boostrapContext和SpringAppication中的主程序既AppApplication,进入starting方法

这里是循环启动这些监听器

在296行上打断点,重启启动debug模式;Step Over进入到prepareEnvironment方法中,也就是准备环境

该方法中先是创建了一个environment,创建环境之后回调listeners,表示环境准备完成

第299行是打印banner也就是启动应用时控制台出现的Spring图标,这个图标是可以自定义的

Step Into 进入 300行的createApplicationContext方法

再次step into进入到create方法

这里根据WebApplication是Servlet类型返回了AnnotationConfigServletWebServerApplicationContext容器

第301行给容器设置启动属性,设置了一个DefaultApplicationStartup

下一行第302行的作用是准备上下文环境,进入到该方法中;这个方法中对容器做了写配置

set环境,注册一些后置处理器,进入applyInitializers方法中

这个方法就是遍历SpringApplication中initializers中所有的initializer,然后调用initialize方法;遍历完成之后来到第382行

进入到contextPrepared方法中

这里就是回调所有的listener的contextPrepared方法

回到prepareContext方法,step over到第389行

这里就是注册命令行参数,并且将banner也注册进来;Step Over到第398行

这里做了判断,然后对容器进行了set操作;来到第406行

这里获取主程序类,然后判断是否为空

preparedContext方法最后一步,所有的listener回调contextLoad方法

至此,容器准备完毕。

step over回到run方法的第303行

step into 进入到refreshContext方法

进入refresh方法

再次进入refresh方法

再次进入refresh方法

这里就是IOC容器的容器初始化方法

实例化所有的单实例Bean,这就是refreshContext方法的作用

回到run方法,来到afterRefresh方法

step into afterRefresh方法

afterRefresh方法为空

再往下的代码是记录时间和日志

step over 到310行

进入callRunnser方法

这方法第753,754行是context容器获取ApplicationRunner和CommandRunner两个类型的Bean

再往下,就是进行回调,最后返回IoC容器。

二、Spring Boot 启动流程总结

run方法启动流程:

  • 准备环境
    • 执行ApplicationContextInitializer.initialize()
    • 监听器SpringApplicationRunListener回调contextPrepared
    • 记载主配置类定义信息
    • 监听器SpringApplicationRunListener回调contextLoaded
  • 刷新启动IoC容器
    • 扫描加载所有容器中的组件
    • 包括从META-INF/spring.factories中获取的所有EnableAutoConfiguration自动配置类
  • 回调容器中所有的ApplicationRunner、CommandLineRunner的run方法
  • 监听器SpringApplicationRunListener回调finished方法

三、Spring Boot 事件监听机制

在启动流程中,有几个监听器非常重要

  • ApplicationContextInitializer
  • SpringApplicationRunListener
  • ApplicationRunner
  • CommandLineRunner 可以自定义监听器实现这些提供的监听器,通过启动应用看这些监听器在什么时候运行

实现自定义的ApplicationContextListener

ApplicationContextListener接口中包含了一个initialize()方法

自定义监听器HalloApplicationContextListener并实现ApplicationContextListener接口,泛型为ConfigurableApplicationContext,既监听IOC容器的启动。

代码语言:javascript
复制
public class HalloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("自定义的" + this.getClass().getName() + "运行了,IoC容器:" + applicationContext);
    }
}

实现自定义的SpringApplicationRunListener

SpringApplicationRunListener接口中的方法都是定义为defualt

实现自定义的HalloSpringApplicationRunListener时不是必须要实现所有的方法

代码语言:javascript
复制
public class HalloSpringApplicationRunListener implements SpringApplicationRunListener {

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println(this.getClass().getSimpleName() + "启动了......");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        String OSName = (String)environment.getSystemProperties().get("获取到的系统名称为:" + "os.name");
        System.out.println(this.getClass().getSimpleName() + "环境准备好了,获取到的系统名称为:" + OSName);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println(this.getClass().getSimpleName() + "IOC容器准备好了.....");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println(this.getClass().getSimpleName() + "容器加载完成.....");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println(this.getClass().getSimpleName() + "容器启动完成,耗时:" + timeTaken);
    }

}

实现自定义的ApplicationRunner

ApplicationRunner接口只只包含了一个run()方法

自定义HalloApplicationRunner实现ApplicationRunner接口

代码语言:javascript
复制
public class HalloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(this.getClass().getSimpleName() + "运行ing....");
    }
}

实现自定义的CommandLineRunner

CommandLineRunner接口中只有一个run()方法

自定义HalloCommandLineRunner类实现CommandLineRunner

代码语言:javascript
复制
public class HalloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println(this.getClass().getSimpleName() + "运行ing,传入的参数为:" + Arrays.asList(args));
    }
}

配置自定义组件

HalloCommandLineRunner和HalloApplicationRunner需要通过添加@Component注解注册到容器中

HalloApplicationContextInitializer和HalloSpringApplicationRunListener 需要配置META-INF/spring.factories配置文件中,在resources目录下新建META-INF/spring.factories配置文件,而配置文件的具体内容可以参看源码中spring.factories中的配置

代码语言:javascript
复制
# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.lilith.listener.HalloApplicationContextInitializer
# Application Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.lilith.listener.HalloSpringApplicationRunListener

测试监听器是否生效

启动应用

控制台报错缺少一个有参构造器

HalloSpringApplicationRunListener中增加一个有参构造器,可以参考SpringApplicationRunListener的另一个实现类EventPublishingRunListener

代码语言:javascript
复制
public HalloSpringApplicationRunListener(SpringApplication application, String[] args) {
}

再次启动应用

控制台打印出了监听器中输出的信息

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Debug Spring Boot 启动流程
    • 创建SpringApplication对象
      • 执行run()方法
      • 二、Spring Boot 启动流程总结
      • 三、Spring Boot 事件监听机制
        • 实现自定义的ApplicationContextListener
          • 实现自定义的SpringApplicationRunListener
            • 实现自定义的ApplicationRunner
              • 实现自定义的CommandLineRunner
                • 配置自定义组件
                  • 测试监听器是否生效
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档