话接上回,在上一篇里我们简单的了解了下如何愉快的阅读springboot3.0的源码,从这一篇开始,我们正式进入我们的源码之旅。
这一节,我们主要探秘springboot的整个启动流程。这也是我们研究springboot源码的重中之重,只有了解了整个启动流程主要做了啥,才能更加方便我们后续的深入理解。
在开始之前,希望大家可以先想想以下几个问题:
1、我们日常开发中使用比较多springboot拓展点是哪些?
2、他们分别在什么场景下使用
3、执行的时间点是什么时候?
1、寻找切入点
正如我们所说的,要找到切入点。太复杂的项目不行,作为切入点的项目要尽可能简单。所以这里我们需要找到一个简单的demo,我们选择springboot源码中的
spring-boot-test
展开其下面的spring-boot-smoke-tests
在该项目下找到
将smoketest.tomcat.SampleTomcatApplication作为我们的切入点,原因无他----这个项目足够简单、干净
代码如下:
总所周知,所有java项目的入口方法都在main方法里,所以我们沿着main方法开始看起。main方法里直接调用了SpringApplication的静态方法run,将当前类的class及启动参数传进去,我们进入run方法继续往下看
继续调用run方法
在这里,完成SpringApplication的实例化,并在实例化完成之后调用其run方法。
代码到这里还是比较简单的,只是调用了几次SpringApplication的run方法,run方法有几个重载,对应不同的入参,但实际上也没干啥。最核心的就是最后这里实例化SpringApplication跟实例化之后调用run。
这里有个要注意的点:springboot项目中,有很多地方虽然看着只是进行实例化,但是实例化的过程中做了很多东西,特别要留意:静态代码块、无参构造、父类构造等,虽然SpringApplication这里并不涉及,但是需要牢记!
2、实例化SpringApplication
我们来看看SpringApplication究竟做了啥? new SpringApplication(primarySources)
这里调用了另一个构造器,注意默认情况下resourceLoader这里传了null,这个方法的注释比较有用,想更加深入了解可以看这个类的类头注释(这也是之前我们说过的要善于利用注释),当然,这里并不是我们这次要了解的重点,所以暂时先忽略。
这个构造器主要做的事情是为一些属性进行赋值,这里有几个核心的地方需要关注下,也就是
从META-INF/spring.factories中获取BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener,并将获取到赋值到类属性中去。这几个也是springboot核心的拓展点,这些想必大家在日常开发中也经常用到,后面我们也会再仔细讲讲。
到这里就已经完成SpringApplication的实例化,接着会调用其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;
}
到这里,我们对整个springboot 3.0的核心启动流程就已经有了大概得认知,整理了下流程图,其中比较核心的,需要仔细研究的的地方也全部通过红色标红,这也是后续我们深入了解的地方。