启动原理

Spring Boot 启动原理详解(基于 2.2.2.RELEASE):从入口到应用就绪的全流程

Spring Boot 的启动过程本质是 “初始化环境→创建上下文→加载 Bean→触发扩展点” 的有序流程,核心入口是 main 方法,通过 SpringApplication 类封装了所有启动逻辑。从 “启动入口→SpringApplication 实例化→run 方法执行→上下文准备与刷新” 四个核心阶段,拆解 Spring Boot 2.2.2.RELEASE 版本的启动原理,帮你理解每个步骤的核心作用。

启动入口:main 方法的 “一键触发”

Spring Boot 应用的启动入口是标注 @SpringBootApplication 的主类中的 main 方法,这行代码是所有启动逻辑的起点:

@SpringBootApplication
public class ConsulApp {
    public static void main(String[] args) {
        // 核心:调用 SpringApplication 的静态 run 方法,传入主类和命令行参数
        SpringApplication.run(ConsulApp.class, args);
    }
}

静态 run 方法的底层逻辑

进入 SpringApplication.run(Class<?> primarySource, String[] args) 源码,发现它做了两件事:

  1. 实例化 SpringApplication:初始化启动所需的核心组件(如监听器、初始化器);
  2. 调用实例的 run 方法:执行实际的启动流程(环境准备、上下文创建、Bean 加载等)。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 1. 实例化 SpringApplication(传入主启动类数组)
    // 2. 调用实例的 run 方法(传入命令行参数)
    return new SpringApplication(primarySources).run(args);
}

阶段一:SpringApplication 实例化 —— 启动前的 “准备工作”

SpringApplication 的构造方法负责初始化启动所需的核心配置,为后续 run 方法执行打基础。构造方法主要完成 6 件事:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 1. 设置资源加载器(默认 null,后续会自动初始化)
    this.resourceLoader = resourceLoader;
    // 2. 校验主启动类不为空(primarySources 是传入的 ConsulApp.class)
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 3. 主启动类去重(用 LinkedHashSet 保证顺序和唯一性)
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 4. 判断 Web 应用类型(核心:决定后续创建哪种上下文)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 5. 加载 ApplicationContextInitializer(上下文初始化器)
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 6. 加载 ApplicationListener(应用监听器)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 7. 推断主启动类(找到包含 main 方法的类)
    this.mainApplicationClass = deduceMainApplicationClass();
}

关键步骤详解

1. 判断 Web 应用类型(WebApplicationType)

WebApplicationType 决定 Spring Boot 后续创建哪种 ApplicationContext(上下文),通过类路径中是否存在特定类来判断:

  • WebApplicationType.NONE:非 Web 应用(类路径中无 javax.servlet.Servletorg.springframework.web.reactive.DispatcherHandler);
  • WebApplicationType.SERVLET:传统 Servlet Web 应用(存在 javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext);
  • WebApplicationType.REACTIVE:响应式 Web 应用(存在 org.springframework.web.reactive.DispatcherHandler)。

源码依据(WebApplicationType.deduceFromClasspath()):

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}
2. 加载初始化器与监听器(SpringFactoriesLoader)

getSpringFactoriesInstances 方法是 Spring Boot 的 “核心加载器”,通过 SpringFactoriesLoader 从类路径下的 META-INF/spring.factories 文件中加载指定接口的实现类:

  • ApplicationContextInitializer:上下文初始化器,用于在上下文刷新前修改配置(如动态添加属性);
  • ApplicationListener:应用监听器,用于监听启动过程中的事件(如 ApplicationStartingEventApplicationEnvironmentPreparedEvent)。

示例:spring.factories 中配置的初始化器和监听器:

# ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer

# ApplicationListener
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.embedded.EmbeddedServletContainerListener
3. 推断主启动类(deduceMainApplicationClass)

通过 “构造运行时异常→遍历异常栈” 的巧妙方式,找到包含 main 方法的类(即我们的 ConsulApp):

private Class<?> deduceMainApplicationClass() {
    try {
        // 构造运行时异常,获取当前线程的栈轨迹
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        // 遍历栈轨迹,找到方法名为 "main" 的栈帧
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                // 通过类名加载类,返回主启动类
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    } catch (ClassNotFoundException ex) {
        // 忽略异常
    }
    return null;
}

阶段二:执行 run 方法 —— 启动的 “核心流程”

SpringApplication 实例化后,调用 run(String... args) 方法,这是 Spring Boot 启动的核心逻辑。run 方法可拆解为 11 个关键步骤,覆盖 “启动前→启动中→启动后” 全链路:

public ConfigurableApplicationContext run(String... args) {
    // NO1:启动计时(记录整个启动耗时)
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // NO2:初始化上下文和异常报告器(异常时用于生成报告)
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置系统属性 java.awt.headless(默认 true,用于无图形界面环境)
    configureHeadlessProperty();

    // NO3:加载 SpringApplicationRunListeners 并触发 starting 事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(); // 广播 "应用开始启动" 事件

    try {
        // NO4:准备应用参数和环境(核心:加载配置、激活环境)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 配置忽略 BeanInfo(避免加载冗余的 Bean 元信息)
        configureIgnoreBeanInfo(environment);

        // NO5:打印 Banner(启动时的 Spring 图标,可自定义)
        Banner printedBanner = printBanner(environment);

        // NO6:创建应用上下文(根据 Web 类型创建对应上下文)
        context = createApplicationContext();
        // 加载异常报告器(用于启动失败时生成报告)
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // NO7:准备上下文(初始化器执行、Bean 加载、事件广播)
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);

        // NO8:刷新上下文(Spring 核心流程,加载 Bean 并初始化)
        refreshContext(context);

        // NO9:刷新后处理(空方法,预留扩展点)
        afterRefresh(context, applicationArguments);
        // 停止计时,打印启动耗时
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 触发 started 事件(广播 "应用上下文已刷新")
        listeners.started(context);

        // NO10:执行 Runner(ApplicationRunner 和 CommandLineRunner)
        callRunners(context, applicationArguments);

    } catch (Throwable ex) {
        // 处理启动失败(触发 failed 事件、生成异常报告)
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    // NO11:触发 running 事件(广播 "应用已就绪")
    try {
        listeners.running(context);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }

    // 返回初始化完成的上下文
    return context;
}

关键步骤详解(核心是 NO4、NO7、NO8、NO10)

1. NO4:准备环境(prepareEnvironment)

prepareEnvironment 是启动的 “配置加载阶段”,负责加载应用的配置(配置文件、系统变量、命令行参数)并激活环境,核心操作:

  • 初始化 ConfigurableEnvironment(根据 Web 类型创建 StandardServletEnvironmentStandardEnvironment);
  • 加载配置源(application.yml/application.properties、命令行参数 --server.port=8081 等);
  • 激活环境(根据 spring.profiles.active 激活对应环境,如 dev/prod);
  • 广播 ApplicationEnvironmentPreparedEvent 事件(通知监听器 “环境已准备完成”)。

源码片段(关键逻辑):

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 1. 创建环境(根据 Web 类型)
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 2. 配置环境(添加命令行参数、系统变量等 PropertySource)
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 3. 激活环境(处理 spring.profiles.active)
    ConfigurationPropertySources.attach(environment);
    // 4. 广播事件(通知监听器环境已准备)
    listeners.environmentPrepared(environment);
    // 5. 绑定环境到 SpringApplication(后续使用)
    bindToSpringApplication(environment);
    return environment;
}
2. NO7:准备上下文(prepareContext)

prepareContext 是 “上下文初始化阶段”,在上下文刷新前完成配置,核心操作:

  • 将环境绑定到上下文(context.setEnvironment(environment));
  • 执行所有 ApplicationContextInitializerinitialize 方法(修改上下文配置);
  • 广播 ApplicationContextInitializedEvent 事件(通知 “上下文已初始化”);
  • 注册特殊 Bean(如 springApplicationArgumentsspringBootBanner);
  • 加载主启动类中的 Bean 定义(扫描 @Component/@Bean 等注解);
  • 广播 ApplicationPreparedEvent 事件(通知 “上下文已准备完成”)。

源码片段(关键逻辑):

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 1. 绑定环境到上下文
    context.setEnvironment(environment);
    // 2. 配置上下文(资源加载器、Bean 名称生成器等)
    postProcessApplicationContext(context);
    // 3. 执行初始化器(ApplicationContextInitializer)
    applyInitializers(context);
    // 4. 广播事件(上下文已初始化)
    listeners.contextPrepared(context);
    // 5. 注册特殊 Bean(应用参数、Banner)
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    // 6. 加载主启动类中的 Bean 定义
    load(context, sources.toArray(new Object[0]));
    // 7. 广播事件(上下文已准备)
    listeners.contextLoaded(context);
}
3. NO8:刷新上下文(refreshContext)

refreshContext 是 Spring Boot 启动的 “核心中的核心”,本质是调用 Spring 原生的 AbstractApplicationContext#refresh() 方法 —— 这是 Spring 容器初始化的标准流程,负责:

  • 初始化 BeanFactory(加载 Bean 定义);
  • 执行 BeanFactoryPostProcessor(修改 Bean 定义,如 @Configuration 类解析);
  • 注册 BeanPostProcessor(Bean 初始化前后增强,如 @Autowired 依赖注入);
  • 初始化消息源、事件广播器;
  • 启动嵌入式容器(如 Tomcat,仅 Web 应用);
  • 实例化所有非懒加载的单例 Bean;
  • 广播 ContextRefreshedEvent 事件。

    refresh() 源码已完整覆盖这些步骤, 核心记住:refresh() 完成后,Spring 容器中的所有 Bean 已初始化完成,应用具备对外提供服务的能力

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
     // 设置beanFactory的一些属性
     // 添加后置处理器
     // 设置忽略的自动装配接口
     // 注册一些组件
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}
4. NO10:执行 Runner(callRunners)

callRunners 是启动的 “最后扩展点”,负责执行所有 ApplicationRunnerCommandLineRunner 实现类的 run 方法 —— 这两个接口用于在应用启动后执行初始化逻辑(如加载缓存、初始化数据)。

核心逻辑:

private void callRunners(ApplicationContext context, ApplicationArguments applicationArguments) {
    List<Object> runners = new ArrayList<>();
    // 1. 收集所有 ApplicationRunner 和 CommandLineRunner Bean
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 2. 按 @Order 或 Ordered 接口排序(数字越小越先执行)
    AnnotationAwareOrderComparator.sort(runners);
    // 3. 遍历执行 run 方法
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, applicationArguments);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, applicationArguments);
        }
    }
}

阶段三:启动完成与事件广播

run 方法执行到最后,会通过 SpringApplicationRunListeners 广播两个关键事件:

  1. started 事件:上下文刷新完成后触发,表示 “应用已启动但 Runner 未执行”;
  2. running 事件:Runner 执行完成后触发,表示 “应用已完全就绪,可对外提供服务”。

若启动过程中抛出异常,会触发 failed 事件,并通过 SpringBootExceptionReporter 生成异常报告,帮助定位问题(如配置错误、依赖缺失)。

总结:Spring Boot 启动的核心链路

结合上述分析,Spring Boot 2.2.2.RELEASE 的启动流程可概括为 “3 大阶段 + 11 个关键步骤”,用一句话总结:

main 方法入口,先实例化 SpringApplication 完成启动前准备(Web 类型判断、初始化器 / 监听器加载),再执行 run 方法加载环境、创建上下文、刷新容器、执行 Runner,最终广播 “应用就绪” 事件,完成启动。

用流程图更直观展示:

main 方法 → SpringApplication 实例化(6步)→ run 方法执行(11步)→ 应用就绪
                ↓                                  ↓
        1. 资源加载器设置                        1. 启动计时
        2. 主启动类去重                        2. 上下文/异常报告器初始化
        3. Web 类型判断                        3. 监听器加载与 starting 事件
        4. 初始化器加载                        4. 环境准备(配置+激活)
        5. 监听器加载                          5. Banner 打印
        6. 主启动类推断                        6. 上下文创建
                                             7. 上下文准备(初始化器+Bean加载)
                                             8. 上下文刷新(Spring 核心流程)
                                             9. 计时停止与 started 事件
                                             10. Runner 执行
                                             11. running 事件广播

理解这一流程,能帮你快速定位启动问题(如环境配置错误、Runner 执行失败),也为自定义启动逻辑(如添加初始化器、监听器)提供基础,是深入 Spring Boot 的关键