『深入学习 Spring Boot』(一) 启动流程概览

1,307 阅读3分钟

初心

为什么想学习 Spring Boot 源码?

第一点:我学习目的很功利,就是为了职业有更好地发展。简单地说,就是下一次换工作时,提高找到好公司的概率;有底气提出更高的工资。

第二点:通过学习源码,提高自己的工程思维、编码技巧等等编码实力。励志写出优雅的代码。

第三点:Spring Boot 是 Java 领域日常使用最多的框架。现在个人只是知道怎么用,对于其内部逻辑一窍不通,内心隐隐不安。

『深入学习 Spring Boot』计划通过输出倒逼输入的形式进行。因本人水平有限,本系列纯属学习笔记。学习过程中会广泛参考网络资料,包括但不限于,掘金、CSDN、博客园、慕课网、百度、谷歌等。

启动流程概览

整个 Spring Boot 容器启动时,基本分为三个大步骤:

graph LR
A(框架初始化) --> B(框架启动) --> C(自动装配)

下面分开,一步步地学习。

框架初始化

标准启动类如下:

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

可以看到调用了 SpringApplication#run,点进去继续看。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }
 
// 注意此处我们进入的是 new SpringApplication() 构造方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
 
public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
 
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  // 资源加载器,null
        this.resourceLoader = resourceLoader;
  // 主类,一般是启动类
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  // 环境监测。判断启动的是 mvc 还是 webflux
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
  // 设置系统初始化器
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  // 设置监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  // 配置 main 函数所在的类
        this.mainApplicationClass = deduceMainApplicationClass();
    }
 

以上方法都在SpringApplication类中,按照调用顺序排列。

SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)中的,便是框架初始化的逻辑。

image-20210509204813276

这里我们需要重点注意两个方法,分别是 设置系统初始化器 与 设置监听器。后面会着重学习。

框架启动

SpringApplication类创建好了,接下是调用run方法,run方法中就是核心的启动流程:

public ConfigurableApplicationContext run(String... args) {
  // 计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
  // 参数准备
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  // 配置 Headless 模式(无显示器、无键盘等系统配置)
        configureHeadlessProperty();
  // 发送启动事件
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
      // 配置环境、发送环境配置完毕事件
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
      // 打印banner
            Banner printedBanner = printBanner(environment);
      // 创建上下文
            context = createApplicationContext();
      // 初始化异常报告器
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
      // 准备上下文(关联 Spring Boot 组件与应用上下文,发送contextPrepared事件,加载sources到context,发送contextLoaded事件等)
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 刷新上下文(实例化Bean等等)
            refreshContext(context);
      // 刷新后置工作
            afterRefresh(context, applicationArguments);
      // 停止计时器
            stopWatch.stop();
      // 日志
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
      // 发送started事件
            listeners.started(context);
      // 执行 runner 类
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
      // 启动失败,处理异常
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
 
        try {
      // 发送running事件
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

上面就是 Spring Boot 启动时的核心方法,几乎每一行都是一个方法,其后隐藏着成百上千行复杂代码。还需要我们慢慢学习。

附框架启动图一张:

image-20210509211309112

自动装配

自动装配实际上,是包含在 框架启动 中的。就是由于这是 Spring Boot 的一个非常非常非常重要的特性以及扩展点,所以还是独立为一个过程。

自动装配的逻辑主要在 **刷新上下文(refresh)**方法中。

自动装配本身的步骤比较少:

image-20210509211923722

总结

这一篇,主要是从全局的角度,粗略地了解一下 Spring Boot 的启动过程。

重点在于,了解 Spring Boot 的启动粗略全过程,至于其中细节,我们后面慢慢学习。