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

2022-09-26 15:57:16 浏览数 (1)


“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) {
}

再次启动应用

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

0 人点赞