SpringBoot 应用启动流程:从启动到 Web 容器初始化

张开发
2026/4/14 5:12:57 15 分钟阅读

分享文章

SpringBoot 应用启动流程:从启动到 Web 容器初始化
上一章我们讲解了 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(); }注意事项•应用类型判断webApplicationTypeSpringBoot 会根据类路径中是否存在 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; CollectionSpringBootExceptionReporter 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. 创建环境对象根据应用类型SERVLET/REACTIVE/NONE创建对应的环境对象如StandardServletEnvironment用于 Servlet 应用2. 加载配置源依次加载系统环境变量、系统属性、命令行参数、application 配置文件application.properties/yml、自定义配置等3. 配置 Profiles激活指定的 Profiles如 dev、test、prod加载对应环境的配置4. 发布事件环境准备完成后发布ApplicationEnvironmentPreparedEvent此时监听器可以获取并修改环境配置比如动态添加配置源。// 环境准备核心代码简化版 ApplicationArguments applicationArguments new DefaultApplicationArguments(args); // 准备环境加载配置、激活 Profiles 等 ConfigurableEnvironment environment prepareEnvironment(listeners, applicationArguments); // 配置忽略的 Bean 信息 configureIgnoreBeanInfo(environment);✅ 注意事项如果启动时报“配置文件加载失败”“配置项不存在”等错误问题通常出在这一步——比如配置文件路径错误、配置项拼写错误、Profiles 激活错误等。步骤3打印 Banner环境准备完成后SpringBoot 会打印我们熟悉的 Banner启动图标这一步是可选的我们可以通过配置spring.banner.enabledfalse关闭 Banner也可以自定义 Banner如在 resources 目录下放置 banner.txt 文件。// 打印 Banner 核心代码 Banner printedBanner printBanner(environment);✅ 小技巧自定义 Banner 时可以通过在线工具生成字符画放入 banner.txt 中启动时就会显示自定义的图标增加项目辨识度。步骤4创建并初始化 Spring 应用上下文ApplicationContext应用上下文ApplicationContext是 Spring 容器的核心负责管理所有 Bean 的生命周期创建、初始化、销毁、依赖注入、事件发布等。这一步会根据应用类型创建对应的上下文实例并完成初始化。核心操作1. 创建上下文根据应用类型选择对应的上下文Servlet 应用对应AnnotationConfigServletWebServerApplicationContext非 Web 应用对应AnnotationConfigApplicationContext2. 配置上下文将环境对象Environment、初始化器ApplicationContextInitializer、监听器ApplicationListener等注入上下文3. 应用初始化器调用所有初始化器的initialize()方法对上下文进行自定义配置4. 发布事件上下文初始化完成后会发布相关事件如ApplicationContextInitializedEvent。// 创建并初始化上下文核心代码 context createApplicationContext(); // 准备上下文注入环境、初始化器、监听器等 prepareContext(context, environment, listeners, applicationArguments, printedBanner);✅ 注意事项上下文的创建是 Spring 容器启动的核心后续的 Bean 加载、依赖注入都依赖于这个上下文对象。如果启动时报“Bean 找不到”“依赖注入失败”大概率是上下文初始化异常导致的。步骤5刷新应用上下文上下文初始化完成后会调用refresh()方法这是整个 Spring 容器初始化的核心操作也是最复杂的一步。refresh()方法会完成 Bean 的扫描、加载、初始化、依赖注入以及 Spring 内置功能的初始化如 AOP、事务管理等。我们重点关注与启动流程相关的核心操作完整的 refresh 流程可参考 Spring 核心源码1. 刷新前准备初始化上下文的生命周期处理器、验证环境配置等2. Bean 定义扫描扫描启动类所在包及其子包下所有标注了Component、Service、Repository、Controller等注解的类将其注册为 Bean 定义3. Bean 初始化实例化所有非懒加载的 Bean完成依赖注入Autowired、初始化方法执行PostConstruct4. 初始化 Spring 内置功能如 AOP 代理、事务管理器、事件广播器等5. 发布ContextRefreshedEvent上下文刷新完成事件此时所有 Bean 已初始化完成Spring 容器正式可用。// 刷新上下文核心代码 refreshContext(context);✅ 注意事项这一步是启动报错的高频区域——比如 Bean 依赖循环、Bean 初始化方法抛出异常、AOP 配置错误等都会导致 refresh 失败从而启动失败。步骤6启动 Web 容器如果是 Web 应用应用类型为 SERVLET 或 REACTIVE上下文刷新完成后会启动对应的 Web 容器默认 Tomcat并将 Web 应用部署到容器中使其能够接收 HTTP 请求。这一步也是 SpringBoot 实现“嵌入式 Web 容器”的核心。核心逻辑以 Tomcat 为例1. 判断应用类型如果是 Web 应用上下文会包含ServletWebServerFactoryWeb 容器工厂用于创建 Web 容器2. 创建 Web 容器通过ServletWebServerFactory创建 Tomcat 容器实例配置容器参数如端口号、上下文路径等读取 application 配置中的server.port等参数3. 部署 Web 应用将 SpringMVC 的DispatcherServlet注册到 Tomcat 容器中负责接收和分发 HTTP 请求4. 启动 Web 容器调用 Tomcat 容器的启动方法绑定端口开始监听 HTTP 请求5. 发布事件Web 容器启动完成后发布ServletWebServerInitializedEventWeb 容器初始化完成事件。// 启动 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 { // 获取命令行参数如 --nametest ListString 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 容器工厂ServletWebServerFactorySpringBoot 通过ServletWebServerFactoryWeb 容器工厂创建 Web 容器实例不同的 Web 容器对应不同的工厂类• TomcatTomcatServletWebServerFactory默认• JettyJettyServletWebServerFactory• UndertowUndertowServletWebServerFactory。SpringBoot 会根据类路径中引入的 Web 容器依赖自动配置对应的工厂类自动配置机制无需手动配置。2. Tomcat 容器初始化步骤1.创建 Tomcat 实例通过TomcatServletWebServerFactory创建 Tomcat 实例设置默认的基础配置如工作目录、连接器等2.配置连接器Connector连接器负责监听 HTTP 请求默认使用 8080 端口支持 HTTP/1.1 协议可通过server.port、server.tomcat.connector.*等配置修改3.创建 Web 应用上下文TomcatWebApplicationContext用于部署 Spring Web 应用关联 Spring 的应用上下文ApplicationContext4.注册 DispatcherServlet将 SpringMVC 的核心DispatcherServlet注册到 Tomcat 中设置其映射路径默认是 “/”负责接收所有 HTTP 请求并分发5.启动 Tomcat 容器调用 Tomcat 实例的start()方法启动连接器和引擎开始监听 HTTP 请求6.绑定端口将 Tomcat 连接器绑定到指定端口默认 8080如果端口被占用启动失败并抛出异常。3. 修改 Web 容器配置日常开发中我们常需要修改 Web 容器的配置如端口、工作目录、连接超时等主要有两种方式方式1通过 application 配置文件# Tomcat 核心配置 server.port8081 # 修改端口为 8081 server.tomcat.uri-encodingutf-8 # 设置 URI 编码 server.tomcat.connection-timeout30000 # 连接超时时间毫秒 server.tomcat.basedir./tomcat-work # Tomcat 工作目录 server.tomcat.max-threads200 # 最大线程数 server.tomcat.min-spare-threads5 # 最小空闲线程数方式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 8080Windows或lsof -i:8080Linux查看端口占用情况释放端口或修改端口•SSL 配置错误如果配置了 HTTPS证书路径错误、证书过期会导致 Tomcat 启动失败•Web 依赖冲突如同时引入 Tomcat 和 Jetty 依赖导致容器工厂冲突•端口号非法端口号需在 1-65535 之间超出范围会启动失败。四、启动流程核心机制理解启动流程的同时还要掌握其背后的核心机制这些是面试中高频提问的重点也是区分“新手”和“老手”的关键。1. 事件驱动机制SpringBoot 启动流程的每一个关键节点都通过发布内置事件驱动后续操作核心事件按触发顺序如下必记1. ApplicationStartingEvent应用启动开始最早2. ApplicationEnvironmentPreparedEvent环境准备完成3. ApplicationContextInitializedEvent应用上下文初始化完成4. ContextRefreshedEvent应用上下文刷新完成Bean 全部初始化5. ServletWebServerInitializedEventWeb 容器初始化完成6. ApplicationStartedEvent应用启动完成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“简化配置、快速开发”的核心思想。后续我们会讲解启动流程中的异常排查技巧帮助大家在实际开发中快速解决启动问题。

更多文章