Spring Boot嵌入式Web容器启动全流程解析:从run()到Tomcat启动

发布时间:2026/5/19 22:58:03

Spring Boot嵌入式Web容器启动全流程解析:从run()到Tomcat启动 1. 项目概述从一行代码到Web服务启动的完整旅程最近在面试辅导和技术复盘时经常被问到Spring Boot的启动流程特别是那个“嵌入式Web容器到底什么时候加载”的问题。很多同学虽然天天用SpringApplication.run(...)这行代码但被问到Tomcat、Jetty这些服务器是怎么悄无声息地跑起来时往往只能说出“自动装配”这个名词具体细节就含糊了。今天我就以一个老开发的身份带大家从run方法入口开始一步步跟进去把Spring Boot启动Web容器的完整脉络彻底理清楚。这篇文章适合所有正在使用Spring Boot的Java开发者无论你是想应对面试还是想深入理解框架原理都能在这里找到答案。2. 启动流程全景与核心阶段划分当我们执行SpringApplication.run(EasyPaasAdminApplication.class, args)这行代码时整个Spring Boot应用就像一台精密的机器开始运转。为了清晰地理解Web容器的加载时机我们首先需要把整个启动过程划分为几个逻辑阶段。通过阅读源码我们可以将其归纳为三个核心阶段准备阶段、应用上下文创建阶段和刷新上下文阶段。嵌入式Web服务器的启动就藏在这最后一个阶段的关键步骤里。这个划分不是凭空想象而是对应着SpringApplication.run()方法内部清晰的责任链。2.1 为什么是这三个阶段这种划分方式源于Spring框架本身的设计哲学。Spring Boot在Spring Framework的基础上做了大量“约定大于配置”的自动化工作但其核心生命周期仍然遵循Spring IoC容器的标准流程准备资源 - 创建容器 - 刷新容器初始化Bean。准备阶段负责解析环境变量、加载外部配置、初始化监听器为容器创建铺平道路。应用上下文创建阶段则根据应用类型Servlet Web、Reactive Web或普通应用实例化对应的ApplicationContext。而最关键的刷新上下文阶段才是所有Bean包括我们的Web服务器Bean被实例化、装配和初始化的地方。理解这个宏观流程是定位Tomcat启动时机的第一步。3. 准备阶段启动的基石与环境搭建准备阶段是启动流程的序幕发生在SpringApplication.run()方法的前半部分。这个阶段的目标是为后续的容器创建准备一个“舞台”包括计时、监听器通知、环境准备等。虽然这个阶段还没有直接创建Web服务器但它所做的准备工作尤其是环境变量的解析直接决定了后续Web服务器以何种配置被加载。3.1 环境准备与监听器机制我们来看run方法开头的一段关键代码为便于理解已做精简和注释public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch new StopWatch(); stopWatch.start(); // 1. 启动计时器 ConfigurableApplicationContext context null; SpringApplicationRunListeners listeners getRunListeners(args); // 2. 获取运行监听器 listeners.starting(); // 3. 发布“应用开始启动”事件 try { ApplicationArguments applicationArguments new DefaultApplicationArguments(args); // 4. 准备并配置应用环境这是关键 ConfigurableEnvironment environment prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner printBanner(environment); // ... 后续创建上下文 } // ... 后续代码 }这里的prepareEnvironment方法至关重要。它会创建并配置一个ConfigurableEnvironment对象这个对象包含了所有的配置属性比如从application.yml、application.properties、命令行参数、系统环境变量等来源读取的配置。其中就包括了决定Web服务器类型的关键配置server.servlet对于Servlet容器和具体的端口、地址等参数。监听器listeners在此阶段开始工作允许我们在应用生命周期的各个节点插入自定义逻辑。注意很多同学容易忽略环境准备阶段的重要性。实际上如果你在application.properties中设置了server.port8081就是在这个阶段被加载到Environment中的。后续Web服务器工厂如TomcatServletWebServerFactory在创建服务器时会从这个Environment对象中读取配置。如果环境变量没准备好后续的所有自动配置都将失去依据。4. 应用上下文创建阶段容器的蓝图在环境准备就绪后Spring Boot需要创建一个ApplicationContext应用上下文也就是我们常说的Spring IoC容器。这个容器将负责管理应用中所有Bean的生命周期。创建什么样的上下文取决于你的应用类型。4.1 上下文类型的选择逻辑createApplicationContext()方法是这个阶段的核心protected ConfigurableApplicationContext createApplicationContext() { Class? contextClass this.applicationContextClass; if (contextClass null) { switch (this.webApplicationType) { // 根据应用类型决定 case SERVLET: // 对于使用Servlet的Web应用使用AnnotationConfigServletWebServerApplicationContext contextClass Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: // 对于响应式Web应用使用AnnotationConfigReactiveWebServerApplicationContext contextClass Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: // 非Web应用使用AnnotationConfigApplicationContext contextClass Class.forName(DEFAULT_CONTEXT_CLASS); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }关键点在于this.webApplicationType的判定。Spring Boot会在启动时扫描类路径Classpath如果发现了javax.servlet.Servlet或org.springframework.web.context.ConfigurableWebApplicationContext等类就会将应用类型判定为SERVLET从而创建支持Servlet Web服务器的应用上下文。这个判定发生在SpringApplication的构造函数中。对于我们最常见的Spring MVC应用创建的就是AnnotationConfigServletWebServerApplicationContext。这个上下文类是实现嵌入式Web容器自动化的关键它内部包含了对ServletWebServerApplicationContext的特殊处理逻辑为后续刷新阶段启动Tomcat埋下了伏笔。5. 刷新上下文阶段Web容器诞生的时刻这是整个启动流程中最核心、最复杂的阶段所有Bean的实例化、依赖注入、初始化都在这里发生。嵌入式Web服务器的启动正是这个阶段一系列精密操作的结果。入口方法是AbstractApplicationContext.refresh()这是一个定义了模板方法模式的标准Spring生命周期方法。5.1 refresh() 方法的核心步骤refresh()方法包含了十几个标准步骤我们重点关注与Web服务器相关的几个prepareRefresh(): 刷新前的准备工作设置启动时间、激活状态等。obtainFreshBeanFactory(): 获取或刷新内部的BeanFactory。prepareBeanFactory(beanFactory): 对BeanFactory进行标准配置例如设置类加载器、后置处理器等。postProcessBeanFactory(beanFactory):这是一个空方法但子类可以重写。ServletWebServerApplicationContext就在这里进行了关键操作。invokeBeanFactoryPostProcessors(beanFactory):执行所有BeanFactoryPostProcessor。这是“自动装配”魔法发生的地方ConfigurationClassPostProcessor会解析所有Configuration类包括那些由EnableAutoConfiguration引入的自动配置类。registerBeanPostProcessors(beanFactory): 注册Bean的后置处理器。initMessageSource(): 初始化国际化资源。initApplicationEventMulticaster(): 初始化事件广播器。onRefresh():又一个关键的空方法由子类实现特定容器的刷新逻辑。Web服务器的创建就在这里触发registerListeners(): 注册事件监听器。finishBeanFactoryInitialization(beanFactory):初始化所有剩余的单例Bean非懒加载。finishRefresh(): 刷新完成发布相应事件。5.2 自动装配WebServerFactory的注入在第5步invokeBeanFactoryPostProcessors中Spring Boot的自动配置机制开始发力。类路径下的META-INF/spring.factories文件中定义了大量的自动配置类。对于Web应用关键的一个是ServletWebServerFactoryAutoConfiguration。这个自动配置类上标有ConditionalOnClass(ServletRequest.class)和ConditionalOnWebApplication(type Type.SERVLET)等条件注解确保只有在Servlet Web应用中才会生效。它会向容器中注册一个ServletWebServerFactory类型的Bean。具体注册哪个工厂取决于你的依赖如果类路径下有Tomcat.class默认情况则注册TomcatServletWebServerFactory。如果类路径下有Jetty.class且没有Tomcat则注册JettyServletWebServerFactory。如果类路径下有Undertow.class且没有Tomcat则注册UndertowServletWebServerFactory。这个过程完美解释了“无缝切换”的原理Spring Boot通过条件注解和类路径检测动态地决定使用哪个Web服务器工厂Bean。你只需要在pom.xml中更换依赖如将spring-boot-starter-tomcat换成spring-boot-starter-jetty自动配置就会选择对应的工厂。5.3 onRefresh()创建并启动Web服务器第9步的onRefresh()方法是Web服务器启动的“发令枪”。在ServletWebServerApplicationContext类中这个方法被重写Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); // 核心方法创建Web服务器 } catch (Throwable ex) { throw new ApplicationContextException(Unable to start web server, ex); } }跟进createWebServer()方法private void createWebServer() { WebServer webServer this.webServer; ServletContext servletContext getServletContext(); // 如果webServer和servletContext都不存在则开始创建 if (webServer null servletContext null) { // 1. 从容器中获取ServletWebServerFactory ServletWebServerFactory factory getWebServerFactory(); // 2. 使用工厂创建WebServer this.webServer factory.getWebServer(getSelfInitializer()); } else if (servletContext ! null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException(Cannot initialize servlet context, ex); } } initPropertySources(); }这里的getWebServerFactory()方法会从Spring容器中获取我们之前通过自动配置注册的ServletWebServerFactoryBean例如TomcatServletWebServerFactory。然后调用其getWebServer(ServletContextInitializer... initializers)方法。5.4 Tomcat服务器的实例化与启动以TomcatServletWebServerFactory为例我们看看getWebServer做了什么Override public WebServer getWebServer(ServletContextInitializer... initializers) { // 1. 创建Tomcat实例 Tomcat tomcat new Tomcat(); // 2. 配置基础目录 File baseDir (this.baseDirectory ! null) ? this.baseDirectory : createTempDir(tomcat); tomcat.setBaseDir(baseDir.getAbsolutePath()); // 3. 创建Connector连接器并配置端口、地址等从Environment读取配置 Connector connector new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); // 4. 配置Engine和Host configureEngine(tomcat.getEngine()); // ... 其他配置 // 5. 准备Context并将ServletContextInitializer应用到Context上 prepareContext(tomcat.getHost(), initializers); // 6. 返回一个包装了Tomcat的WebServer实现 return getTomcatWebServer(tomcat); }getTomcatWebServer(tomcat)方法会实例化一个TomcatWebServer对象并在其构造函数中调用initialize()方法。在initialize()里最关键的一行是private void initialize() throws WebServerException { // ... 其他代码 this.tomcat.start(); // 启动Tomcat服务器 // ... 其他代码 }至此内嵌的Tomcat服务器正式启动开始监听我们在配置中指定的端口默认为8080。所以嵌入式Web容器的加载和启动精确地说发生在AbstractApplicationContext.refresh()方法的第9步——onRefresh()调用期间。6. 核心源码解析与关键类梳理为了让大家更清晰地把握整个过程我们梳理一下涉及的核心类和它们扮演的角色类名所属包核心职责在流程中的位置SpringApplicationorg.springframework.boot启动类负责整个启动流程的编排。启动入口执行run方法。AnnotationConfigServletWebServerApplicationContextorg.springframework.boot.web.servlet.contextServlet Web应用的上下文重写了onRefresh()以创建Web服务器。应用上下文创建阶段实例化刷新阶段执行onRefresh。ServletWebServerFactoryAutoConfigurationorg.springframework.boot.autoconfigure.web.servlet自动配置类根据条件向容器注册ServletWebServerFactoryBean。刷新阶段invokeBeanFactoryPostProcessors时被处理。TomcatServletWebServerFactoryorg.springframework.boot.web.embedded.tomcatTomcat服务器工厂负责创建和配置内嵌Tomcat实例。作为Bean被自动配置注册在createWebServer()中被获取并调用。TomcatWebServerorg.springframework.boot.web.embedded.tomcat对Tomcat的包装管理Tomcat的生命周期启动/停止。由TomcatServletWebServerFactory.getWebServer()创建并返回。关键交互流程SpringApplication.run()启动。创建AnnotationConfigServletWebServerApplicationContext。调用context.refresh()。在refresh()的invokeBeanFactoryPostProcessors步骤处理ServletWebServerFactoryAutoConfiguration将TomcatServletWebServerFactory注册为Bean。在refresh()的onRefresh()步骤ServletWebServerApplicationContext调用createWebServer()。createWebServer()从容器获取TomcatServletWebServerFactoryBean。调用factory.getWebServer(...)创建并启动TomcatWebServer。TomcatWebServer内部初始化并调用Tomcat.start()。7. 常见问题与排查技巧实录在实际开发和面试中围绕嵌入式Web容器启动会遇到一些典型问题。这里我结合自己的经验分享几个常见场景和排查思路。7.1 端口被占用导致启动失败这是最常见的问题。Spring Boot启动时如果发现配置的端口默认为8080被其他进程占用会抛出WebServerException。排查步骤确认错误信息控制台通常会明确打印“Port 8080 was already in use”。查找占用进程Linux/Mac: 在终端执行lsof -i:8080或netstat -tulpn | grep 8080。Windows: 在命令行执行netstat -ano | findstr :8080找到PID后在任务管理器中查看对应进程。解决方案终止占用进程如果是不需要的进程直接终止。修改应用端口在application.properties中设置server.port另一个端口如8081。启动时随机端口设置server.port0Spring Boot会分配一个随机可用端口启动日志中会打印具体端口号。实操心得在微服务架构或本地同时启动多个服务实例时端口冲突频发。我习惯在本地开发时为每个服务在application.yml中配置一个特定的端口范围并利用IDE的“Modify Run Configuration”功能在启动参数中动态覆盖server.port非常方便。7.2 应用无法切换至Jetty或Undertow想从默认的Tomcat切换到Jetty按照文档添加了spring-boot-starter-jetty依赖并排除了spring-boot-starter-tomcat但启动后发现用的还是Tomcat。排查思路检查依赖树使用Maven的mvn dependency:tree或Gradle的./gradlew dependencies命令查看spring-boot-starter-tomcat是否真的被排除了。有时候其他传递性依赖可能会再次引入它。检查自动配置报告在application.properties中设置debugtrue重启应用。控制台会打印一份详细的自动配置报告Auto-configuration report。搜索“ServletWebServerFactory”看是哪个工厂类被激活了Positive matches以及Tomcat工厂是否被排除Negative matches。确认条件注解JettyServletWebServerFactory的自动配置类上通常有ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})。确保你的类路径下确实存在这些Jetty核心类即spring-boot-starter-jetty依赖正确引入。正确的Maven排除示例dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jetty/artifactId /dependency7.3 Web服务器启动慢或卡住有时应用启动时会在“Starting embedded Tomcat server”这一步停留很久。可能原因与优化Session持久化扫描如果使用了Tomcat的Session持久化到磁盘的功能且目录下文件很多启动时会扫描这些文件。检查server.tomcat.basedir配置。随机数熵源Entropy Source不足在Linux环境下Tomcat/Spring Boot启动时需要生成随机数用于Session ID等如果系统熵值低如虚拟机SecureRandom的生成会阻塞。这是一个经典坑点。解决方案在JVM启动参数中添加-Djava.security.egdfile:/dev/./urandom。注意这里是/dev/./urandom不是/dev/urandom中间的点号有特定作用。大量Servlet/Filter注册如果通过ServletContextInitializer动态注册了大量Servlet或Filter初始化可能会变慢。考虑懒加载或优化注册逻辑。DNS解析问题如果服务器配置了主机名hostname且DNS解析缓慢或失败也可能导致启动延迟。检查/etc/hosts文件配置。7.4 如何自定义Web服务器配置除了修改server.port等通用属性我们经常需要深度定制Tomcat等服务器。几种常用方式通过application.properties/yml配置Spring Boot提供了大量server.tomcat.*、server.jetty.*、server.undertow.*的配置项如连接数、线程池、压缩等。这是最推荐的方式。server: tomcat: max-connections: 10000 max-threads: 200 min-spare-threads: 10 connection-timeout: 5000通过WebServerFactoryCustomizerBean这是一个更强大的接口允许你以编程方式定制工厂。Component public class MyTomcatCustomizer implements WebServerFactoryCustomizerTomcatServletWebServerFactory { Override public void customize(TomcatServletWebServerFactory factory) { factory.addConnectorCustomizers(connector - { // 定制Connector如设置协议处理器 if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol) { ((AbstractHttp11Protocol?) connector.getProtocolHandler()).setMaxKeepAliveRequests(100); } }); factory.addContextCustomizers(context - { // 定制Context context.setSessionTimeout(30, TimeUnit.MINUTES); }); } }直接暴露ServletWebServerFactoryBean这种方式会完全接管自动配置提供的工厂需要你完成所有配置慎用。Bean public ServletWebServerFactory servletWebServerFactory() { TomcatServletWebServerFactory factory new TomcatServletWebServerFactory(); factory.setPort(9090); // ... 进行大量配置 return factory; }**选择建议**优先使用配置文件对于配置文件不支持的特定需求再考虑使用WebServerFactoryCustomizer。除非有非常特殊的定制需求否则不建议直接定义工厂Bean以免失去Spring Boot自动配置的便利性。 理解Spring Boot嵌入式Web容器的加载时机和原理不仅仅是应对面试更能帮助我们在遇到启动异常、性能调优、深度定制等复杂场景时快速定位问题根源。从SpringApplication.run()那一行简单的代码开始到Tomcat在指定的端口上开始监听HTTP请求这背后是一套设计精巧、层次分明的自动化流程。掌握它你就能真正驾驭Spring Boot的启动过程而不仅仅是停留在会用的层面。

相关新闻