springboot3.0 源码揭秘 ----启动流程

229 阅读4分钟

话接上回,在上一篇里我们简单的了解了下如何愉快的阅读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的核心启动流程就已经有了大概得认知,整理了下流程图,其中比较核心的,需要仔细研究的的地方也全部通过红色标红,这也是后续我们深入了解的地方。