深入解析 Spring Boot 启动过程都做了些什么?

642 阅读5分钟

1.概述

Spring Boot 提供了简化的配置和自动化功能,使开发者能够快速构建应用。但在 Spring Boot 的背后,启动过程涉及一系列复杂的流程,包括 Bean 的加载、环境配置、自动配置的注册等。这篇博文将详细总结 Spring Boot 的启动过程,并结合源码解析每一步具体做了什么。

2.Spring Boot启动过程

当我们启动一个 Spring Boot 应用时,入口通常是一个带有 @SpringBootApplication 注解的类。@SpringBootApplication是自动配置的核心所在,详细总结可查看之前梳理的文章:Spring Boot自动配置原理详解和自定义封装实现starter

在启动过程中,会发生以下主要步骤:

  1. 启动引导:SpringApplication 实例创建
  2. 准备应用上下文
  3. 加载和解析配置文件
  4. 创建并刷新 IOC 容器
  5. 执行初始化器、监听器、Runner 等组件
  6. 启动完成事件通知

接下来我们就分别来看看这些步骤都具体干了些啥。

2.1 启动引导:创建 SpringApplication 实例

我们都知道一个应用的启动入口类通常包含如下代码:

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

SpringApplication.run() 是启动的核心方法。在源码中:

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

可以看出这里的核心操作就是创建 SpringApplication 实例,设置启动args参数执行#run()方法。

2.2 准备应用上下文

在构造 SpringApplication 对象时即new SpringApplication(primarySources),会判断应用的类型(ServletReactiveNone),并初始化环境。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 确定web应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    // 加载初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 加载监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
  }

核心处理操作如下:

  • 确定 Web 应用类型:根据类路径(如是否存在 DispatcherServlet)判断是 Web、Reactive 还是 CLI 应用。
  • 加载初始化器:从 META-INF/spring.factories 中加载所有 ApplicationContextInitializer
  • 加载监听器:从 META-INF/spring.factories 中加载 ApplicationListener,用于监听事件。

SpringApplication实例对象构建好之后,同时意味着上下文也准备好了,接下来就是执行#run()方法:这是启动的核心逻辑所在:

  public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 环境准备
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      // 创建容器
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      // 刷新容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      // 启动完成事件通知
      listeners.started(context, timeTakenToStartup);
      // 
      callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
    }
    try {
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      listeners.ready(context, timeTakenToReady);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
    }
    return context;
  }

2.3 环境准备:加载和解析配置文件

Spring Boot 需要读取 外部配置文件(如 application.propertiesapplication.yml)并将其解析为环境变量。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
        "Environment prefix cannot be set via properties.");
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
      EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
      environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
  }

关键步骤:

  • 创建或获取 Environment 实例:使用 StandardEnvironment 创建默认环境。
  • 解析配置文件:读取 application.ymlapplication.properties
  • 发布环境事件:调用 listeners.environmentPrepared() 通知所有监听器,环境已准备完毕。

2.4 创建并刷新 IOC 容器

2.4.1 创建IOC容器

在 Spring Boot 中,ApplicationContext 是 IOC 容器的核心。在启动时,会根据应用类型创建适当的上下文类(如 AnnotationConfigServletWebServerApplicationContext)。

private ConfigurableApplicationContext createApplicationContext() {
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(this.applicationContextClass);
}

上下文创建流程:

  • 判断上下文类型:Servlet Web 应用使用 AnnotationConfigServletWebServerApplicationContext
  • 创建上下文实例:通过反射实例化上下文类。
  • 关联环境与上下文:将 Environment 注入上下文。

2.4.2 刷新IOC容器

IOC 容器的创建和刷新是核心步骤。该步骤会实例化所有需要的 Bean,并完成依赖注入。

​
private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
}

refresh() 是核心方法,定义在 AbstractApplicationContext 中:

@Override
public void refresh() throws BeansException, IllegalStateException {
    // 1. 准备上下文环境
    prepareRefresh();
​
    // 2. 创建 BeanFactory 并加载所有 Bean 定义
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
​
    // 3. 初始化 BeanFactory
    prepareBeanFactory(beanFactory);
​
    // 4. 注册 BeanPostProcessor
    registerBeanPostProcessors(beanFactory);
​
    // 5. 初始化 MessageSource、事件广播器等组件
    initMessageSource();
    initApplicationEventMulticaster();
​
    // 6. 注册监听器并广播早期事件
    registerListeners();
​
    // 7. 初始化所有非延迟加载的单例 Bean
    finishBeanFactoryInitialization(beanFactory);
​
    // 8. 完成刷新流程
    finishRefresh();
}

解析

  • 加载 Bean 定义:从类路径或 XML 文件中读取所有 Bean 定义。
  • 创建 BeanFactory:创建 IOC 容器,用于管理 Bean 实例。
  • 注册 BeanPostProcessor:允许在 Bean 初始化前后执行自定义逻辑。
  • 广播事件:通知所有事件监听器。
  • 初始化所有单例 Bean:创建并注入非懒加载的 Bean。

2.5 执行初始化操作

在 Spring Boot 启动过程中,ApplicationRunnerCommandLineRunner 会在容器初始化完成后执行。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
​
    for (Object runner : runners) {
        if (runner instanceof ApplicationRunner) {
            ((ApplicationRunner) runner).run(args);
        } else if (runner instanceof CommandLineRunner) {
            ((CommandLineRunner) runner).run(args.getSourceArgs());
        }
    }
}

用于执行启动后的初始化逻辑,如缓存预加载、任务调度等

2.6 启动完成事件通知

当所有初始化工作完成后,Spring Boot 会发布 ApplicationReadyEvent,通知应用已经启动完成。

listeners.running(context);

ApplicationReadyEvent 可以用于执行某些需要在应用完全就绪后运行的逻辑,比如注册监控组件。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址github.com/plasticene/…

Gitee地址gitee.com/plasticene3…

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

3.总结

Spring Boot 的启动过程虽然看似简单,但背后涉及大量的逻辑处理,包括环境配置、IOC 容器初始化、事件广播、Bean 生命周期管理等。在实际开发中,理解这些流程可以帮助我们更好地优化应用的启动性能,以及在合适的阶段执行初始化逻辑。希望本文的详细讲解能帮助你深入理解 Spring Boot 的启动原理和源码实现。