“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大概有这几步:
- 保存主配置类
- 判断是否为Web应用
- 从类路径下找到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) {
}
再次启动应用
控制台打印出了监听器中输出的信息