SpringBoot 如何实现 main 方法启动 Web 容器的?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官抛出这个问题,绝不仅仅是想听一句 “因为 SpringBoot 内嵌了 Tomcat”。他希望通过这个问题,考察你对 SpringBoot 核心设计思想和启动流程的理解深度。具体来说,考察点包括:
- 对 SpringBoot “约定大于配置” 和 “内嵌容器” 核心理念的理解:面试官想知道你是否明白 SpringBoot 是如何通过简化部署和配置来改变传统 Java Web 开发的。
- 对
SpringApplication.run()方法启动全流程的掌握:不仅仅是知道这个方法,更要清楚这个方法背后做了什么,经历了哪些关键阶段。 - 对内嵌 Web 容器(如 Tomcat, Jetty)自动装配和启动机制的源码级理解:这是本题的技术核心。你需要知道 SpringBoot 是如何“发现”并“创建”一个 Web 容器的。
- 实际应用与问题排查能力:理解原理后,你是否能在实际项目中根据需求定制容器(如修改端口、线程池参数),或是在启动失败时进行有效的问题定位。
核心答案
Spring Boot 通过其独特的 “内嵌容器” 设计和 SpringApplication 引导类,在 main 方法中一键式启动完整的 Web 应用。其本质是:将传统需要外部部署的 Web 服务器(如 Tomcat)作为库(JAR 包)内嵌到应用中,并在 Spring 容器刷新过程的最后阶段,自动实例化并启动这个内嵌服务器。
启动入口 SpringApplication.run(Application.class, args) 会创建一个 ApplicationContext(通常是 AnnotationConfigServletWebServerApplicationContext),并在其 refresh() 方法的最后一步,调用 onRefresh() 钩子方法来启动内嵌的 Web 容器。
深度解析
下面我们拆解这个 “魔法” 是如何一步步实现的。
原理与机制
整个过程可以概括为以下几个关键步骤:
- 启动引导 (
SpringApplication.run()):初始化SpringApplication实例,推断应用类型(这里是SERVLET即 Web 应用),加载ApplicationContextInitializer和ApplicationListener,最后调用Context的refresh()方法。 - 上下文准备与刷新:创建并配置
AnnotationConfigServletWebServerApplicationContext。这个上下文类是实现 Web 启动的关键,它继承了ServletWebServerApplicationContext。 - 触发容器创建钩子 (
onRefresh()):在AbstractApplicationContext.refresh()的标准生命周期中,会调用子类实现的onRefresh()方法。ServletWebServerApplicationContext重写了此方法。 - 自动装配与工厂创建:在
onRefresh()中,会调用createWebServer()方法。这个方法的核心是:- 获取
ServletWebServerFactory:通过getWebServerFactory()从 Spring 容器中获取这个工厂 Bean。这个 Bean 是由 自动配置类ServletWebServerFactoryAutoConfiguration在满足条件(类路径下有相关 Jar)时自动创建的。例如,引入了spring-boot-starter-web会触发 Tomcat 的自动配置,创建TomcatServletWebServerFactory。 - 利用工厂创建
WebServer:调用工厂的getWebServer(ServletContextInitializer... initializers)方法。这个方法会实例化一个内嵌的 Tomcat/Jetty/Undertow 对象,进行端口、上下文路径等基本配置,并将 Spring 中所有的Servlet、Filter、Listener通过ServletContextInitializer回调接口注册到该容器的ServletContext中。
- 获取
- 容器启动:工厂返回一个已配置好但未启动的
WebServer(如TomcatWebServer)实例。随后,调用其start()方法,内嵌的 Tomcat 线程池启动,开始监听端口,等待 HTTP 请求。
代码示例
1. 最简启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 组合注解,开启组件扫描和自动配置
public class MyApplication {
public static void main(String[] args) {
// 这行代码触发了上述所有流程
SpringApplication.run(MyApplication.class, args);
}
}
2. 自定义内嵌 Tomcat 配置(展示原理关联):
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyTomcatConfig {
// 通过定制工厂来修改容器行为
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> webServerFactoryCustomizer() {
return factory -> {
factory.setPort(8081); // 修改端口
factory.addConnectorCustomizers(connector -> {
// 自定义连接器参数,如线程池
// connector.setAttribute("maxThreads", 200);
});
};
}
}
最佳实践与注意事项
- 选择容器:在
spring-boot-starter-web中,默认使用 Tomcat。你可以通过排除spring-boot-starter-tomcat并引入spring-boot-starter-jetty或spring-boot-starter-undertow来切换,SpringBoot 的自动配置会据此调整。 - 性能调优:在生产环境中,务必通过
application.yml或代码方式对容器(尤其是线程池)进行调优,例如设置server.tomcat.max-threads、max-connections等参数。 - 优雅关机:确保应用启用了优雅关机(
server.shutdown=graceful),并在收到终止信号时,让 Spring Boot 先停止接收新请求,处理完存量请求后再关闭容器。
常见误区
- “SpringBoot 用的是特殊版本的 Tomcat”:误区。它用的就是标准的 Tomcat 核心库,只是打包和启动方式被封装了。
- “需要手动编写
web.xml或Servlet初始化的代码”:不需要。Spring Boot 通过DispatcherServletAutoConfiguration自动注册了核心的DispatcherServlet,其他Servlet和Filter只需声明为 Spring Bean 即可自动注册。 - “
main方法启动只适用于开发”:完全错误。这是 Spring Boot 推荐的标准部署方式,通过java -jar运行的独立 JAR 包,正是利用了内嵌容器,简化了生产环境的部署和运维。
总结
Spring Boot 通过 SpringApplication 的统一引导、ServletWebServerApplicationContext 的 onRefresh 钩子、以及 ServletWebServerFactoryAutoConfiguration 的自动装配机制,无缝地集成并启动了内嵌的 Web 容器,最终实现了从一个普通的 main 方法启动一个功能完备的 Web 应用,完美体现了其 “开箱即用” 的核心设计哲学。