上一章我们讲解了 SpringBoot 事件机制,其实 SpringBoot 自身的启动全流程,就是一场“事件驱动”的完整实践。很多同学日常开发中,只知道“运行 main 方法就能启动项目”,但对其底层启动逻辑一知半解——比如 main 方法到底做了什么?Spring 容器是何时初始化的?Web 容器(Tomcat、Jetty)又是如何被启动的?配置文件是何时加载的?

理解 SpringBoot 启动流程,不仅能帮我们快速定位启动报错(如配置加载失败、Bean 初始化异常、Web 容器启动失败等),更是面试高频考点SpringBoot 启动流程是什么?SpringBoot 如何集成 Web 容器?)。

一、启动入口:main 方法的核心作用

SpringBoot 应用的启动入口,就是我们最熟悉的 main方法,通常写在标注了 @SpringBootApplication注解的启动类中。看似简单的一行代码,背后隐藏着整个启动流程的入口逻辑。

1. 启动类示例

// 启动类:标注 @SpringBootApplication,作为应用启动入口
@SpringBootApplication
public class SpringBootDemoApplication {
    // 启动入口:main 方法
    public static void main(String[] args) {
        // 核心代码:启动 SpringBoot 应用
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

2. 核心入口:SpringApplication.run() 方法

整个 SpringBoot 启动流程,都是从 SpringApplication.run() 方法开始的。该方法看似简单,实则做了两件核心事情:

  • • 创建 SpringApplication 实例:初始化应用的核心配置(如判断应用类型、加载初始化器、监听器等);

  • • 调用 SpringApplication.run() 重载方法:执行具体的启动流程(环境准备、上下文初始化、Web 容器启动等)。

3. 源码解析:SpringApplication 实例创建

当我们调用 SpringApplication.run(SpringBootDemoApplication.class, args) 时,首先会创建 SpringApplication 实例,其构造方法会完成一系列初始化操作:

// SpringApplication 构造方法(简化版,核心逻辑)
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    // 1. 校验启动类(primarySources),不能为空
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 2. 判断应用类型(至关重要:是 Web 应用还是非 Web 应用)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 3. 加载 Spring 内置的初始化器(ApplicationContextInitializer)
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 4. 加载 Spring 内置的监听器(ApplicationListener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 5. 推断 main 方法所在的启动类(即我们写的 SpringBootDemoApplication)
    this.mainApplicationClass = deduceMainApplicationClass();
}
注意事项
  • • 应用类型判断(webApplicationType):SpringBoot 会根据类路径中是否存在 Web 相关依赖(如 Tomcat、Spring Web 依赖),自动判断应用类型:

    • • SERVLET:传统 Web 应用(依赖 spring-webmvc、tomcat 等),会启动 Servlet 容器(Tomcat 默认);

    • • REACTIVE:响应式 Web 应用(依赖 spring-webflux),会启动响应式 Web 容器;

    • • NONE:非 Web 应用(无 Web 依赖),不启动任何 Web 容器。

  • • 初始化器(ApplicationContextInitializer):用于在 Spring 容器(ApplicationContext)初始化前,对容器进行自定义配置(如设置环境变量、添加自定义 Bean 定义等),Spring 内置了多个初始化器,也支持自定义。

  • • 监听器(ApplicationListener):加载 Spring 内置的监听器(如监听启动事件、上下文刷新事件等),这些监听器会在启动流程的不同节点被触发,驱动后续操作(比如上一章讲的 ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent 等)。

二、核心启动流程:从初始化到应用就绪

创建完 SpringApplication 实例后,会调用其 run() 方法,这是整个启动流程的核心。我们将其拆解为 8 个关键步骤,每一步都对应启动流程的一个核心操作,结合源码和实战细节,逐个解析。

步骤1:启动计时与发布“应用启动开始事件”

启动流程的第一步,是初始化启动计时器(用于统计整个启动耗时),并发布 ApplicationStartingEvent(应用启动开始事件)——这是 SpringBoot 启动过程中最早触发的事件,此时 Spring 容器还未初始化,环境配置也未准备。

// SpringApplication.run() 方法核心逻辑(简化版)
public ConfigurableApplicationContext run(String... args) {
    // 1. 初始化启动计时器(统计启动耗时)
    StopWatch stopWatch = new StopWatch();
    stopWatch.start(); // 开始计时
    
    // 2. 初始化应用上下文(ApplicationContext)和异常报告器
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
    // 3. 配置系统属性(如 headless 模式,避免图形化依赖)
    configureHeadlessProperty();
    
    // 4. 获取 SpringApplication 监听器(之前构造方法中加载的)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 5. 发布 ApplicationStartingEvent(应用启动开始事件)
    listeners.starting();
    
    try {
        // 后续步骤...
    } catch (Throwable ex) {
        // 异常处理...
    }
}

✅ 注意事项:此时监听器可以接收 ApplicationStartingEvent,但由于容器和环境未初始化,无法进行依赖注入、配置加载等操作,通常用于启动前的系统参数初始化。

步骤2:准备环境(Environment)

环境准备是启动流程的核心步骤之一,SpringBoot 会创建并配置 Environment(环境对象),加载所有配置信息(配置文件、系统环境变量、命令行参数等),并发布 ApplicationEnvironmentPreparedEvent(环境准备完成事件)。

环境准备的核心操作的包括:

  1. 1. 创建环境对象:根据应用类型(SERVLET/REACTIVE/NONE),创建对应的环境对象(如 StandardServletEnvironment 用于 Servlet 应用);

  2. 2. 加载配置源:依次加载系统环境变量、系统属性、命令行参数、application 配置文件(application.properties/yml)、自定义配置等;

  3. 3. 配置 Profiles:激活指定的 Profiles(如 dev、test、prod),加载对应环境的配置;

  4. 4. 发布事件:环境准备完成后,发布 ApplicationEnvironmentPreparedEvent,此时监听器可以获取并修改环境配置(比如动态添加配置源)。

// 环境准备核心代码(简化版)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境:加载配置、激活 Profiles 等
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置忽略的 Bean 信息
configureIgnoreBeanInfo(environment);

✅ 注意事项:如果启动时报“配置文件加载失败”“配置项不存在”等错误,问题通常出在这一步——比如配置文件路径错误、配置项拼写错误、Profiles 激活错误等。

步骤3:打印 Banner

环境准备完成后,SpringBoot 会打印我们熟悉的 Banner(启动图标),这一步是可选的,我们可以通过配置 spring.banner.enabled=false 关闭 Banner,也可以自定义 Banner(如在 resources 目录下放置 banner.txt 文件)。

// 打印 Banner 核心代码
Banner printedBanner = printBanner(environment);

✅ 小技巧:自定义 Banner 时,可以通过在线工具生成字符画,放入 banner.txt 中,启动时就会显示自定义的图标,增加项目辨识度。

步骤4:创建并初始化 Spring 应用上下文(ApplicationContext)

应用上下文(ApplicationContext)是 Spring 容器的核心,负责管理所有 Bean 的生命周期(创建、初始化、销毁)、依赖注入、事件发布等。这一步会根据应用类型,创建对应的上下文实例,并完成初始化。

核心操作:
  1. 1. 创建上下文:根据应用类型选择对应的上下文(Servlet 应用对应AnnotationConfigServletWebServerApplicationContext,非 Web 应用对应 AnnotationConfigApplicationContext);

  2. 2. 配置上下文:将环境对象(Environment)、初始化器(ApplicationContextInitializer)、监听器(ApplicationListener)等注入上下文;

  3. 3. 应用初始化器:调用所有初始化器的 initialize() 方法,对上下文进行自定义配置;

  4. 4. 发布事件:上下文初始化完成后,会发布相关事件(如 ApplicationContextInitializedEvent)。

// 创建并初始化上下文核心代码
context = createApplicationContext();
// 准备上下文:注入环境、初始化器、监听器等
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

✅ 注意事项:上下文的创建是 Spring 容器启动的核心,后续的 Bean 加载、依赖注入,都依赖于这个上下文对象。如果启动时报“Bean 找不到”“依赖注入失败”,大概率是上下文初始化异常导致的。

步骤5:刷新应用上下文

上下文初始化完成后,会调用 refresh() 方法,这是整个 Spring 容器初始化的核心操作,也是最复杂的一步。refresh() 方法会完成 Bean 的扫描、加载、初始化、依赖注入,以及 Spring 内置功能的初始化(如 AOP、事务管理等)。

我们重点关注与启动流程相关的核心操作(完整的 refresh 流程可参考 Spring 核心源码):

  1. 1. 刷新前准备:初始化上下文的生命周期处理器、验证环境配置等;

  2. 2. Bean 定义扫描:扫描启动类所在包及其子包下,所有标注了 @Component@Service@Repository@Controller 等注解的类,将其注册为 Bean 定义;

  3. 3. Bean 初始化:实例化所有非懒加载的 Bean,完成依赖注入(@Autowired)、初始化方法执行(@PostConstruct);

  4. 4. 初始化 Spring 内置功能:如 AOP 代理、事务管理器、事件广播器等;

  5. 5. 发布 ContextRefreshedEvent(上下文刷新完成事件):此时所有 Bean 已初始化完成,Spring 容器正式可用。

// 刷新上下文核心代码
refreshContext(context);

✅ 注意事项:这一步是启动报错的高频区域——比如 Bean 依赖循环、Bean 初始化方法抛出异常、AOP 配置错误等,都会导致 refresh 失败,从而启动失败。

步骤6:启动 Web 容器

如果是 Web 应用(应用类型为 SERVLET 或 REACTIVE),上下文刷新完成后,会启动对应的 Web 容器(默认 Tomcat),并将 Web 应用部署到容器中,使其能够接收 HTTP 请求。这一步也是 SpringBoot 实现“嵌入式 Web 容器”的核心。

核心逻辑(以 Tomcat 为例):
  1. 1. 判断应用类型:如果是 Web 应用,上下文会包含 ServletWebServerFactory(Web 容器工厂),用于创建 Web 容器;

  2. 2. 创建 Web 容器:通过 ServletWebServerFactory 创建 Tomcat 容器实例,配置容器参数(如端口号、上下文路径等,读取 application 配置中的 server.port 等参数);

  3. 3. 部署 Web 应用:将 SpringMVC 的 DispatcherServlet 注册到 Tomcat 容器中,负责接收和分发 HTTP 请求;

  4. 4. 启动 Web 容器:调用 Tomcat 容器的启动方法,绑定端口,开始监听 HTTP 请求;

  5. 5. 发布事件:Web 容器启动完成后,发布 ServletWebServerInitializedEvent(Web 容器初始化完成事件)。

// 启动 Web 容器核心代码(简化版)
private void finishRefresh() {
    // 初始化生命周期处理器
    initLifecycleProcessor();
    // 触发所有 Bean 的生命周期方法(start)
    getLifecycleProcessor().onRefresh();
    // 发布上下文刷新完成事件
    publishEvent(new ContextRefreshedEvent(this));
    // 如果是 Web 应用,启动 Web 容器
    WebServer webServer = startWebServer();
    if (webServer != null) {
        // 发布 Web 容器初始化完成事件
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}
注意事项:
  • • 嵌入式 Web 容器:SpringBoot 之所以无需手动部署到外部 Tomcat,是因为它将 Tomcat、Jetty 等 Web 容器嵌入到应用中,通过代码启动容器,简化部署流程;

  • • 端口配置:Web 容器的端口默认是 8080,可通过 server.port 配置修改;如果端口被占用,会抛出 BindException,需修改端口或释放占用端口;

  • • 容器切换:默认使用 Tomcat,可通过排除 Tomcat 依赖、引入 Jetty 或 Undertow 依赖,实现 Web 容器的切换(如微服务场景中,Undertow 性能更优)。

步骤7:执行自定义 Runner(初始化任务)

Web 容器启动完成后,SpringBoot 会执行自定义的初始化任务——即实现了 CommandLineRunner 或 ApplicationRunner 接口的 Bean。这是我们在应用启动完成后,执行自定义逻辑(如加载缓存、初始化字典、发送启动通知)的常用方式。

两种 Runner 对比:
  • • CommandLineRunner:接收命令行参数(String[] args),参数是原始的命令行字符串;

  • • ApplicationRunner:接收 ApplicationArguments 对象,可更方便地获取命令行参数(如选项参数、非选项参数)。

// 示例1:实现 CommandLineRunner
@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // 启动完成后执行的逻辑(如加载缓存)
        System.out.println("CommandLineRunner 执行:应用启动完成,加载缓存中...");
    }
}

// 示例2:实现 ApplicationRunner
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 获取命令行参数(如 --name=test)
        List<String> nameArgs = args.getOptionValues("name");
        System.out.println("ApplicationRunner 执行:命令行参数 name=" + nameArgs);
    }
}

✅ 注意事项:如果有多个 Runner,可通过@Order 注解指定执行顺序(值越小,优先级越高)。

步骤8:发布“应用启动完成事件”,启动结束

所有初始化操作(上下文刷新、Web 容器启动、Runner 执行)完成后,SpringBoot 会发布 ApplicationStartedEvent(应用启动完成事件)和ApplicationReadyEvent(应用就绪事件),并停止启动计时器,打印启动耗时。

  • • ApplicationStartedEvent:应用已启动完成,所有 Bean 已初始化,Web 容器已启动,但服务尚未完全就绪;

  • • ApplicationReadyEvent:应用完全就绪,可正常接收和处理 HTTP 请求(这是启动流程的最后一个事件)。

// 启动完成核心代码(简化版)
listeners.started(context);
// 执行 Runner
callRunners(context, applicationArguments);
// 发布应用就绪事件
listeners.ready(context);
// 停止计时器,打印启动耗时
stopWatch.stop();
if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

✅ 最终效果:控制台会打印“Started SpringBootDemoApplication in X.X seconds (JVM running for X.X)”,表示应用启动完成,可正常提供服务。

三、Web 容器初始化的详细拆解

对于 Web 应用来说,Web 容器的初始化是启动流程中最关键的环节之一,也是面试中高频提问的点。我们以默认的 Tomcat 容器为例,详细拆解其初始化和启动流程,搞懂“SpringBoot 如何启动 Tomcat 并部署应用”。

1. Web 容器工厂(ServletWebServerFactory)

SpringBoot 通过 ServletWebServerFactory(Web 容器工厂)创建 Web 容器实例,不同的 Web 容器对应不同的工厂类:

  • • Tomcat:TomcatServletWebServerFactory(默认);

  • • Jetty:JettyServletWebServerFactory

  • • Undertow:UndertowServletWebServerFactory

SpringBoot 会根据类路径中引入的 Web 容器依赖,自动配置对应的工厂类(自动配置机制),无需手动配置。

2. Tomcat 容器初始化步骤

  1. 1. 创建 Tomcat 实例:通过 TomcatServletWebServerFactory 创建 Tomcat 实例,设置默认的基础配置(如工作目录、连接器等);

  2. 2. 配置连接器(Connector):连接器负责监听 HTTP 请求,默认使用 8080 端口,支持 HTTP/1.1 协议,可通过 server.portserver.tomcat.connector.* 等配置修改;

  3. 3. 创建 Web 应用上下文(TomcatWebApplicationContext):用于部署 Spring Web 应用,关联 Spring 的应用上下文(ApplicationContext);

  4. 4. 注册 DispatcherServlet:将 SpringMVC 的核心 DispatcherServlet 注册到 Tomcat 中,设置其映射路径(默认是 “/”),负责接收所有 HTTP 请求并分发;

  5. 5. 启动 Tomcat 容器:调用 Tomcat 实例的 start() 方法,启动连接器和引擎,开始监听 HTTP 请求;

  6. 6. 绑定端口:将 Tomcat 连接器绑定到指定端口(默认 8080),如果端口被占用,启动失败并抛出异常。

3. 修改 Web 容器配置

日常开发中,我们常需要修改 Web 容器的配置(如端口、工作目录、连接超时等),主要有两种方式:

方式1:通过 application 配置文件
# Tomcat 核心配置
server.port=8081 # 修改端口为 8081
server.tomcat.uri-encoding=utf-8 # 设置 URI 编码
server.tomcat.connection-timeout=30000 # 连接超时时间(毫秒)
server.tomcat.basedir=./tomcat-work # Tomcat 工作目录
server.tomcat.max-threads=200 # 最大线程数
server.tomcat.min-spare-threads=5 # 最小空闲线程数
方式2:通过自定义 ServletWebServerFactory

适用于更复杂的配置(如添加自定义连接器、修改引擎配置等):

@Configuration
public class TomcatConfig {
    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        // 创建 Tomcat 容器工厂
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        // 修改端口
        factory.setPort(8081);
        // 配置连接器
        factory.addAdditionalTomcatConnectors(createConnector());
        return factory;
    }

    // 自定义连接器(如添加 HTTPS 连接器)
    private Connector createConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setPort(8443); // HTTPS 端口
        connector.setScheme("https");
        connector.setSecure(true);
        // 配置 SSL 证书(省略细节)
        return connector;
    }
}

4. 常见 Web 容器启动失败原因

  • • 端口被占用:最常见原因,通过 netstat -ano | findstr 8080(Windows)或 lsof -i:8080(Linux)查看端口占用情况,释放端口或修改端口;

  • • SSL 配置错误:如果配置了 HTTPS,证书路径错误、证书过期会导致 Tomcat 启动失败;

  • • Web 依赖冲突:如同时引入 Tomcat 和 Jetty 依赖,导致容器工厂冲突;

  • • 端口号非法:端口号需在 1-65535 之间,超出范围会启动失败。

四、启动流程核心机制

理解启动流程的同时,还要掌握其背后的核心机制,这些是面试中高频提问的重点,也是区分“新手”和“老手”的关键。

1. 事件驱动机制

SpringBoot 启动流程的每一个关键节点,都通过发布内置事件驱动后续操作,核心事件按触发顺序如下(必记):

  1. 1. ApplicationStartingEvent:应用启动开始(最早);

  2. 2. ApplicationEnvironmentPreparedEvent:环境准备完成;

  3. 3. ApplicationContextInitializedEvent:应用上下文初始化完成;

  4. 4. ContextRefreshedEvent:应用上下文刷新完成(Bean 全部初始化);

  5. 5. ServletWebServerInitializedEvent:Web 容器初始化完成;

  6. 6. ApplicationStartedEvent:应用启动完成;

  7. 7. ApplicationReadyEvent:应用就绪(最后)。

2. 自动配置机制

SpringBoot 启动流程中,很多配置(如 Web 容器工厂、环境配置、Bean 扫描等)都是通过自动配置机制完成的,无需手动配置。核心是 @EnableAutoConfiguration 注解(被 @SpringBootApplication 注解包含),通过扫描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载所有自动配置类。

比如:Web 容器的自动配置(ServletWebServerFactoryAutoConfiguration)、SpringMVC 的自动配置(WebMvcAutoConfiguration)等,都是通过自动配置机制加载的。

3. 应用上下文分层

SpringBoot 启动过程中,会创建两个核心上下文,分工明确:

  • • 启动上下文(BootstrapContext):用于加载启动过程中需要的核心配置(如配置中心的配置、加密解密配置等),优先级高于应用上下文;

  • • 应用上下文(ApplicationContext):用于管理应用中的所有 Bean、依赖注入、事件发布等,是日常开发中接触最多的上下文。

掌握本章内容,不仅能快速定位启动过程中的各种问题,还能从容应对面试中的相关提问,更能深入理解 SpringBoot“简化配置、快速开发”的核心思想。后续我们会讲解启动流程中的异常排查技巧,帮助大家在实际开发中快速解决启动问题。

更多推荐