QQ: 408365330
email: egojit@qq.com
3. springboot2.2.x源码学习-run的启动流程
整个springboot程序都是从这个方法开始的,所以它的逻辑复杂调用链也比较长,我们这里先梳理一个大的调用架构,然后后续针对这些调用架构我们一个一个的深入进去学习和阅读它们的源码
run代码总览:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
// 1.1 springboot程序启动时间统计-开始位置
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 1.2 通过springboot的factories机制获取SpringApplicationRunListener配置实现类
SpringApplicationRunListeners listeners = getRunListeners(args);
// 触发listeners的 starting
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 1.3 创建应用上下文
context = createApplicationContext();
// 1.4 通过springboot的factories机制获取全局的错误异常处理实现类
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 1.5上下文环境准备
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 1.6 刷新上下文环境
refreshContext(context);
// 1.7上下文环境刷新后
afterRefresh(context, applicationArguments);
// 统计执行时间的结束位置
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 1.8 执行ApplicationRunner和CommandLineRunner实现类的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
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;
}
3.1 springboot程序启动时间统计
通过StopWatch 统计线性的springboot程序的启动实际,方便我们调试的时候发现springboot的启动耗时,这样能看出是不是程序初始化的时候有什么问题;注意,这里的线性启动时间,指主线程的启动时间;从其中可以看出,这里面springboot程序启动慢可能的情况比较多,例如bean初始化慢,listeners相关生命周期执行慢,等等;默认这个统计时间是会打印出来的,也就是logStartupInfo默认是true;启动springboot程序后会打印
Started ProxyBootApplication in 68.809 seconds (JVM running for 82.632)
如果想去掉这些日志怎么呢?如果看懂之前的源码你就知道可以这样写:用上面的三行代码替换下面默认的一行代码;这就是阅读源码的好处,基于springboot的程序哪怕再变态的需求你也能搞定,因为你知道springboot内部原理,而不仅仅只是会常规的去使用
// 打印日志
SpringApplication.run(ProxyBootApplication.class, args);
// 不打印日志
SpringApplication application= new SpringApplication(ProxyBootApplication.class);
application.setLogStartupInfo(false);
application.run(args);
1.2 通过factories机制获取SpringApplicationRunListener配置实现类,并且触发starting生命周期
springboot的factories机制,通过获取读取所有jar包中的spring.factories文件中配置的SpringApplicationRunListener实现类;只要实现了这个接口的类都包含一定的生命周期事件;(在这里我这么称呼它们)
springboot默认的jar包中读取的是EventPublishingRunListener类;这个类用ApplicationEventMulticaster在上下文context实际刷新之前发布SpringApplicationEvent事件;具体这些事件是干什么用的;后续进行详细的抽丝剥茧。这里我们走代码大纲;
触发SpringApplicationRunListener的 starting周期方法;根据这个原理我们可以实现自己的SpringApplicationRunListener事件广播处理,并且在这些生命周期干一些事情;这个starting生命周期是在context刷新之前执行
1.3 创建应用上下文
这个很重要,这里根据前一篇博客中初始化的应用类型webApplicationType创建应用上下文;由前面的博客我们知道springboot版本 2++以上支持三种web应用类型分别是:servlet, reactive, none这三种类型,有什么区别请看上一篇博客;
1.4 通过springboot的factories机制获取全局的错误异常处理实现类
通过factories机制获取 SpringBootExceptionReporter的实现类,默认获取到的org.springframework.boot.diagnostics.FailureAnalyzers这个类的实例;这实例用于处理springboot启动异常;现在如果我们有这样一个需求就是把springboot程序启动的错误通过网络汇报到数据库或者专门的日志系统,我们该怎么做?其实看到这里我们已经有很好的方法了;我们可以实现一个SpringBootExceptionReporter接口的子类;并且实现这个接口的boolean reportException(Throwable failure)方法,在这个方法中把错误提交到日志数据库或日志系统
1.5上下文环境准备
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
这里主要是设置bean加载器,我们可以深入知道支持注解,xml配置等方式;加载把beans加载到spring 上下文环境中去设置环境变了environment;这里我们走流程结构不关注细节,后续博客重点关注这个方法;
1.6 刷新上下文环境
refreshContext(context);
这个方法很核心,很多springboot的特性都在这里面进行处理了;如果你是reactive类型web
会调用AnnotationConfigReactiveWebServerApplicationContext的refresh方法
如果是Servlet类型的springboot程序会调用AnnotationConfigServletWebServerApplicationContext的refresh方法
1.7上下文环境刷新后
afterRefresh(context, applicationArguments);
这是一个空方法,我们不去关注它
1.8 执行ApplicationRunner和CommandLineRunner实现类的run方法
callRunners(context, applicationArguments);
我们可以从上面看出来,这两个接口的实现类的run 方法在上下文环境准备好后执行的,也就是这个run方法中我们可以使用环境变量,可以使用context中初始化好的bean;因为bean已经被装载到了上下文容器中了;
如果我们有这样一个需求,就是在springboot程序启动成功后要执行我们的一段代码逻辑,而这个代码逻辑中用了bean注入;这样一个场景我们很适合用这两个接口的实现类来做