springboot启动流程
springboot启动分为两大步:1.创建SpringApplication对象。2.执行这个对象的run方法。
先看第一步做了什么事:
1.创建SpringApplication对象:
这其实就是一个构造器。
重点描述这几个部分:
1.确定应用程序的类型,
方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程);
2.加载 ApplicationContextInitializer
这里加载的初始化器是springboot自带初始化器,从从 META-INF/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure 项目 和 spring-boot 项目里面各有一个。
在这里我们也可以自定义初始化器,只要实现这个接口就可以。
3.加载 ApplicationListener
载监听器也是从 META-INF/spring.factories 配置文件中加载的
4.设置应用程序的主类
deduceMainApplicationClass(); 这个方法是找到main方法所在的类,为后面的扫描包作准备。
至此第一部分结束,SpringApplication对象创建成功
2.执行SpringApplication对象的run方法
public ConfigurableApplicationContext run(String... args) {// stopwatch的中文翻译是秒表的意思// 创建一个秒表用于记录容器的启动时间StopWatch stopWatch = new StopWatch();// 秒表开启记时stopWatch.start();// applicationContext的上下文对象ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();/**第一步:加载spring.factories文件中的所有SpringApplicationRunListener对象这个对象是事件发布者,他在springBoot启动的不同阶段会发布不同的事件SpringApplicationRunListener接口只有一个实现类EventPublishingRunListener**/SpringApplicationRunListeners listeners = getRunListeners(args);/**第二步:run方法刚开始执行的事件广播即,触发事件ApplicationStartingEvent,所有监听了事件ApplicationStartingEvent的ApplicationListener都会在此时执行**/listeners.starting();try {/**第三步:创建并配置当前应用将要使用的环境1. 加载命令行2. 命令行中可能存在指定环境变量的参数--spring.active.profile=dev,所以需要传入参数applicationArguments3. 在创建环境完成够需要发布事件environmentPrepared,所以需要传入参数listeners**/ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);/**第四步:打印banner信息**/Banner printedBanner = printBanner(environment);/**第五步:创建applicationContext基于反射技术,根据应用类型创建了applicationContext对象,但是并没有设置applicationContext的属性到了这一步中context中有一个environment属性,但是其不是springApplication对象中的environment属性// environment.getProperty("test.string") = test// context.getEnvironment.getProperty("test.string") = null**/context = createApplicationContext();/**第六步:创建异常报告器1. 获取定义在spring.factories中的SpringBootExceptionReporter类的对象2. SpringBootExceptionReporter接口只有一个实现类FailureAnalyzers3. FailureAnalyzers会获取spring.factories定义的FailureAnalyzer类的对象 **/exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);/**第七步:初始化容器对象(初始化applicationContext)1.将第三步创建的环境设置到applicationContext里面去在这一步之后applicationContext中的environment等于springApplication对象中的environment2.调用ApplicationContextInitailizer接口applyInitializers(context);3.发布ApplicationContextInializer执行完毕事件 ApplicationContextInitializedEventlisteners.contextPrepared(context);4.输出容器启动的前两条日志// 2023-08-26 20:59:38.282 INFO 76061 --- [ main] com.qiha.testquartz.Main : Starting Main on MBYP46525326.local with PID 76061 (/Users/cheng/IdeaProjects/qiha/test-spring-boot-start/build/classes/java/main started by cheng in /Users/ cheng/IdeaProjects/qiha)// 2023-08-26 20:59:38.350 INFO 76061 --- [ main] com.qiha.testquartz.Main : No active profile set, falling back to default profiles: default// 可以通过配置参数 spring.main.log-startup-info=false 设置是否输出if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}5.将启动参数applicationArguments保存到容器中6.将printedBanner保存到容器中7.设置是否允许相同beanName的bean覆盖可以通过参数spring.main.allow-bean-definition-overriding=true设置(spring-boot中默认是false)if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}8.是否开启懒加载,可以通过参数spring.main.lazy-initialization=true设置在dev环境中可以开启,加快调试速度可以在JVM参数上加-Dspring.main.lazy-initialization=true启动可以在JVM参数加参数,然后参数会被解析之后存储到environment中比如-Dtest.c=c 可以environment.getProperty("test.c")9.将启动类加入到applicationContext容器中10.发布事件ApplicationPreparedEvent,表示容器已经准备好了**/prepareContext(context, environment, listeners, applicationArguments, printedBanner);/**第八步:刷新容器1. 注册应用程序关闭的钩子函数spring.main.register-shutdown-hook=false,默认是开启的,一般开启就好了2. 刷新容器,详见3.8**/refreshContext(context);/**第九步:刷新容器后操作**/afterRefresh(context, applicationArguments);// SpringBoot启动结束,关闭秒表,并且日志打印出启动时长// 可以通过配置参数 spring.main.log-startup-info=false 设置是否输出stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {/**异常报告器的用处!!!发布事件 ApplicationFailedEvent**/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;
}
主要流程:
1.开启计时器:
用于计算springboot项目启动的时间
2将java.awt.headless设置为true:
表示作为服务器运行,不需要显示器等输入输出设备也能运行
3.创建并开启SpringApplicationRunListener
监听器:
目的是
在SpringBoot启动的不同阶段会发布相应的监听通知。通知贯穿应用启动的全过程。
4.将执行run方法时传入的参数封装成一个对象。
其实就是我们传入的那个args。
5.准备环境变量:
包含系统属性和用户配置的属性,比如说我们系统的环境变量。
6.打印banner信息
这个我们可以自定义,只要在resource里面放一个banner.txt就会自动使用我们定义的banner
7. 创建 ApplicationContext
容器:
会根据 WebApplicationType 创建不同类型的容器
8.
实例化异常报告器:
这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常。
9.准备上下文环境:
9.1--执行初始化方法:还记得第3步里面加载的初始化器嘛?其实是执行第3步加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类。就是我们上面加载的那些类
9.2--动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments
10.刷新上下文-----这里其实就是加载bean
==这个过程实际上就是 Spring 容器的核心:==
涉及的关键方法:
-
invokeBeanFactoryPostProcessors
-
registerBeanPostProcessors
-
instantiateBeans
-
finishBeanFactoryInitialization
-
finishRefresh
⭕ 扫描 @Component、@Configuration,实例化 Bean,进行依赖注入等
11.刷新上下文的后置处理
12.结束计时器
13.发布上下文准备就绪事件
参考:9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂-腾讯云开发者社区-腾讯云