深度解析Springboot的启动流程

组里的实习生妹妹讲了一次Springboot的启动,

讲着讲着就讲到Springbean的生命周期去了,

我心想坏了,

这妮子估计把SpringbootSpring的相关逻辑给混淆了,

这必须得给她治一治。


前言

本文会对Springboot启动流程进行详细分析。但是请注意,Springboot启动流程是Springboot的逻辑,请千万不要将Springboot启动流程相关逻辑与Spring的相关逻辑混在一起,比如把Springbean生命周期的逻辑混在Springboot启动流程中,那么整个体系就复杂且混乱了。

所以本文仅重点关注Springboot启动流程,涉及Spring的部分,会略作说明并跳过。

整体的一个结构图如下。

Springboot版本:2.4.1

正文

一. Springboot启动流程图及说明

如下是Springboot的一个启动流程图。

SpringApplication完成初始化后,就会调用SpringApplication对象的run() 方法,该方法就是Springboot启动的入口,也对应着全流程图中的开始。下面给出SpringApplication对象的run() 方法说明,如下所示。

public ConfigurableApplicationContext run(String... args) {
    // 创建StopWatch,用于统计Springboot启动的耗时
    StopWatch stopWatch = new StopWatch();
    // 开始计时
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    // 获取运行时监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 调用运行时监听器的starting()方法
    // 该方法需要在Springboot一启动时就调用,用于特别早期的初始化
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 获取args参数对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 读取Springboot配置文件并创建Environment对象
        // 这里创建的Environment对象实际为ConfigurableEnvironment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印Banner图标
        Banner printedBanner = printBanner(environment);
        // 创建ApplicationContext应用行下文,即创建容器
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 准备容器
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 初始化容器
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        // 停止计时
        stopWatch.stop();
        if (this.logStartupInfo) {
            // 打印启动耗时等信息
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 调用运行时监听器的started()方法
        // 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 调用运行时监听器的running()方法
        // 该方法需要在SpringApplication的run()方法执行完之前被调用
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
复制代码

二. SpringApplication的初始化

通常,Springboot应用程序的启动类定义如下。

@SpringBootApplication
public class LearnStartApplication {

    public static void main(String[] args) {
        SpringApplication.run(LearnStartApplication.class, args);
    }

}
复制代码

SpringApplication的静态run() 方法一路跟进,会发现如下的实现。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}
复制代码

也就是Springboot启动时会先创建SpringApplication,然后再通过SpringApplicationrun() 方法完成启动。所以下面分析一下SpringApplication的初始化逻辑,其构造方法如下所示。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 设置源
    // 通常Springboot的启动类就是源
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推断并设置WEB应用程序类型
    // 根据classpath下的类来推断
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载并设置Bootstrapper
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    // 加载并设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 加载并设置应用事件监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推断并设置应用程序主类的Class对象
    this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

梳理一下在SpringApplication的构造方法中,做了如下事情。

  1. 设置源。通常,Springboot中的源就是Springboot的启动类;
  2. 设置WEB应用程序类型。通过判断classpath下是否存在某些类,来推断当前WEB应用程序的类型;
  3. 加载并设置BootstrapperApplicationContextInitializerApplicationListener。借助SpringFactoriesLoader基于SPI机制完成BootstrapperApplicationContextInitializerApplicationListener的加载,然后设置到SpringApplication中;
  4. 设置应用程序主类的Class对象。

下面对上述事情进行分析。

1. 设置源

这里的源,也就是Spring容器启动时依赖的初始配置类,在Springboot中,初始配置类通常为启动类。下面可以通过调试看一下primarySources字段的值,如下所示。

可见源就是Springboot的启动类的Class对象。

2. 设置WEB应用程序类型

WebApplicationType#deduceFromClasspath方法如下所示。

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值