在上一篇,我们已经通过demo程序了解了怎么去自定义BootstrapRegistryInitializer,且通过控制台的输出,也能大概了解到其执行过程。这一篇,我们正式开始深入剖析springboot的运行流程。
springboot项目的真正运行逻辑,是放在初始化SpringApplicationContext之后,调用了run方法,在简单了解springboot启动流程那一篇,我们也简单的看了run方法的内部实现,这里再次贴下代码:
public ConfigurableApplicationContext run(String... args) {
// 创建startUp,springboot3.2引入了Crac,可用于检查点恢复,默认情况下,只返回StandardStartup,用于记录时间
Startup startup = Startup.create();
// 属性赋值,为后面refreshContext添加jvm钩子做准备,默认情况下是启用的
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
// 创建上下文,里面会调用BootstrapRegistryInitializer
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 设置java.awt.headless,当 java.awt.headless 设置为 true 时,Java 程序将不能使用依赖于 AWT 或 Swing 的 GUI 组件
// 一般的springboot项目均不需要gui,所以这里设置为true
configureHeadlessProperty();
// 拿到所有的监听器,并调用starting方法
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 处理启动参数,仅是简单将启动参数封装成ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 打印启动banner
Banner printedBanner = printBanner(environment);
// 创建applicantContext
context = createApplicationContext();
// 设置启动使用 steps 控制应用程序启动阶段。
context.setApplicationStartup(this.applicationStartup);
// 准备各种context
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文,添加jvm shutdown钩子
refreshContext(context);
// 上下文刷新之后的处理
afterRefresh(context, applicationArguments);
// 设置启动完成的时间
startup.started();
// 打印启动耗时
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
// 应用启动后,执行listeners
listeners.started(context, startup.timeTakenToStarted());
// 执行runner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
这一篇,我们主要着手于Startup对象的创建以及其创建之后用到的场景
1 、Startup对象的创建
run方法的第一行,就是StartUp对象的创建
Startup startup = Startup.create();
我们看看create方法做了啥:
static Startup create() {
// 获取类加载器
ClassLoader classLoader = Startup.class.getClassLoader();
// 用类加载器判断是否支持CRaC
return (ClassUtils.isPresent("jdk.crac.management.CRaCMXBean", classLoader)
&& ClassUtils.isPresent("org.crac.management.CRaCMXBean", classLoader))
? new CoordinatedRestoreAtCheckpointStartup() : new StandardStartup();
}
代码非常简单:先获取类加载器,然后尝试加载jdk.crac.management.CRaCMXBean、org.crac.management.CRaCMXBean看看是否支持crac,如果支持,创建的CoordinatedRestoreAtCheckpointStartup,否则创建StandardStartup。
我们来看看抽象类Startup,主要定义了些啥?
abstract static class Startup {
private Duration timeTakenToStarted;
/**
* 获取启动时间
* @return
*/
protected abstract long startTime();
/**
* 获取jvm启动耗时
* @return
*/
protected abstract Long processUptime();
/**
* 获取处理动作,这里是返回字符串,crac的方式返回的不同
* @return
*/
protected abstract String action();
/**
* 标志程序已启动,记录启动花费的时间 - 创建StartUp创建的时间
* @return
*/
final Duration started() {
long now = System.currentTimeMillis();
this.timeTakenToStarted = Duration.ofMillis(now - startTime());
return this.timeTakenToStarted;
}
/**
* 获取启动过程所花费的时间
* @return
*/
Duration timeTakenToStarted() {
return this.timeTakenToStarted;
}
/**
* 记录springboot可以对外响应的时间
* @return
*/
private Duration ready() {
long now = System.currentTimeMillis();
return Duration.ofMillis(now - startTime());
}
static Startup create() {
// 获取类加载器
ClassLoader classLoader = Startup.class.getClassLoader();
// 用类加载器判断是否支持CRaC
return (ClassUtils.isPresent("jdk.crac.management.CRaCMXBean", classLoader)
&& ClassUtils.isPresent("org.crac.management.CRaCMXBean", classLoader))
? new CoordinatedRestoreAtCheckpointStartup() : new StandardStartup();
}
}
代码很简单,猜也能猜出来是用来记录springboot启动各个阶段的耗时。实现类也比较简单,这里不过多累述。CoordinatedRestoreAtCheckpointStartup跟StandardStartup的区别在于引入crac之后,有些时间的计算不用。CoordinatedRestoreAtCheckpointStartup中也使用StandardStartup做兜底。在创建Startup对象的时候,会记录启动时间
private final Long startTime = System.currentTimeMillis();
2、Startup对象的使用
我们知道,Startup对象主要是用来记录springboot启动各个阶段的耗时。关注Startup其实作用不大 。我们主要是想通过Startup的使用,来明确springboot启动过程的各个阶段是怎样的?比如:什么时候springboot项目才算启动完成?什么时候才算准备好。所以我们继续往后看看用到Startup的地方。
我们可以看到,run方法调用后,会执行很多操作,直到刷新上下文、以及刷新上下文后的后置处理完成之后,才会认为项目已经启动完成。之后,记录日志,再执行应用启动完的listener、再执行runner,等到都执行完成了,才会认为springboot进入ready状态。这几个时机,在我们日常开发的去自定义listener、runner非常有用。
总的来说,Startup主要用来记录时间,方便最终打印整个应用的启动耗时(或用在其他地方,反正就是记录时间用到)