Spring5 全家桶 | 45 - Spring Boot 2.x 启动流程解析

1,706 阅读7分钟

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

一、Debug Spring Boot 启动流程

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

image.png

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

image.png

创建SpringApplication对象

首先会创建SpringApplication对象

image.png

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

Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

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

this.webApplicationType = WebApplicationType.deduceFromClasspath();

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

image.png

debug到这个方法中去

image.png

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

this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

image.png

image.png

image.png

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

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

this.bootstrapRegistryInitializers = new ArrayList<>(
      getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

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

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

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

image.png

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

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

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

image.png

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

image.png

再往下进行debug

image.png

确定了主程序

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

创建SpringApplication大概有这几步:

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

执行run()方法

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

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

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

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

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

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

SpringApplicationRunListeners listeners = getRunListeners(args);

Step Into 进入到 getRunListeners方法

image.png

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

image.png

这里获取了一个EventPublishRunListener

继续Step Over到294行

listeners.starting(bootstrapContext, this.mainApplicationClass);

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

image.png 这里是循环启动这些监听器

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

image.png

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

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

image.png

Step Into 进入 300行的createApplicationContext方法 image.png 再次step into进入到create方法 image.png 这里根据WebApplication是Servlet类型返回了AnnotationConfigServletWebServerApplicationContext容器

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

下一行第302行的作用是准备上下文环境,进入到该方法中;这个方法中对容器做了写配置 image.png set环境,注册一些后置处理器,进入applyInitializers方法中

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

image.png

进入到contextPrepared方法中 image.png 这里就是回调所有的listener的contextPrepared方法

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

image.png 这里就是注册命令行参数,并且将banner也注册进来;Step Over到第398行 image.png 这里做了判断,然后对容器进行了set操作;来到第406行 image.png 这里获取主程序类,然后判断是否为空

preparedContext方法最后一步,所有的listener回调contextLoad方法 image.png 至此,容器准备完毕。

step over回到run方法的第303行

image.png

step into 进入到refreshContext方法

image.png 进入refresh方法

image.png 再次进入refresh方法

image.png 再次进入refresh方法

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

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

回到run方法,来到afterRefresh方法

image.png

step into afterRefresh方法

afterRefresh方法为空

image.png

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

step over 到310行

image.png

进入callRunnser方法 image.png

这方法第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()方法 image.png

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

public class HalloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

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

实现自定义的SpringApplicationRunListener

SpringApplicationRunListener接口中的方法都是定义为defualt image.png 实现自定义的HalloSpringApplicationRunListener时不是必须要实现所有的方法

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()方法 image.png

自定义HalloApplicationRunner实现ApplicationRunner接口

public class HalloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(this.getClass().getSimpleName() + "运行ing....");
    }
}

实现自定义的CommandLineRunner

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

自定义HalloCommandLineRunner类实现CommandLineRunner

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中的配置

image.png

# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.lilith.listener.HalloApplicationContextInitializer
# Application Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.lilith.listener.HalloSpringApplicationRunListener

测试监听器是否生效

启动应用 image.png 控制台报错缺少一个有参构造器

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

image.png

public HalloSpringApplicationRunListener(SpringApplication application, String[] args) {
}

再次启动应用

image.png

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