内容提要
文章围绕 Spring Boot 启动原理,解答三个问题:一是启动后持续运行,是因为创建了非守护线程池,JVM 需等用户线程结束才退出;二是能切换 Tomcat、Jetty 等容器,靠条件注解按依赖自动选择对应的 ServletWebServerFactory;三是 main 函数启动 Web 容器,是在 refresh 的 onRefresh 阶段创建并初始化 WebServer,底层创建线程池并监听端口。核心方法是带着问题 debug 跟踪源码验证。
延伸解读
非守护线程:应用持续运行的底层原因
文章指出,Spring Boot 启动后没有 while(true) 却一直运行,是因为启动了线程池,其中包含非守护线程。JVM 规范要求等所有用户线程结束才退出,因此只要这些线程还在,进程就不会终止。这解释了 Web 容器监听端口的本质:创建线程池和套接字,由线程处理请求。理解这一点,有助于排查应用为何无法正常退出。
条件注解如何实现 Web 容器自动切换
Spring Boot 能切换 Tomcat、Jetty 等容器,靠的是 ServletWebServerFactoryAutoConfiguration 中的条件注解。源码逻辑是:Tomcat 依赖存在就用 Tomcat,否则按顺序检查 Jetty、Undertow。因此,在 pom 中 exclusion 掉 Tomcat 并引入 Jetty 后,条件满足的工厂类变为 JettyServletWebServerFactory。这体现了自动配置的依赖驱动特性。
main 函数启动 Web 容器的关键阶段
文章跟踪源码发现,SpringApplication.run 创建 AnnotationConfigServletWebServerApplicationContext 后,在 refresh 的 onRefresh 阶段调用 createWebServer。ServletWebServerApplicationContext 重载了 onRefresh,增加创建 Web 服务的逻辑,通过 getWebServerFactory 获取工厂并 new WebServer,进而初始化容器、创建线程池和监听端口。这解释了
带着问题 debug:阅读源码的有效方法
文章强调,正文看似逐步回答问题,实际在演示阅读 Spring 源码的方法:带着问题去看,用 debug 跟踪源码验证。作者以三个具体问题开场,通过跟踪 JettyServletWebServerFactory、onRefresh 等关键类和方法,逐一验证猜想。这种方法避免空泛阅读,让原理理解有据可查,对学习复杂框架有实际参考价值。
Q&A
Spring Boot 应用启动后为什么能一直运行,没有 while(true) 也不会退出?
因为启动过程中创建了非守护线程池,JVM 会等待所有用户线程结束后才退出,所以主线程结束后应用仍持续运行。
Spring Boot 如何实现 Tomcat、Jetty 等 Web 容器的自动切换?
通过条件注解(如 @ConditionalOnClass)根据类路径中存在的依赖自动选择对应的 ServletWebServerFactory。例如存在 Tomcat 依赖则用 Tomcat,否则按顺序检查 Jetty、Undertow。
Spring Boot 的 main 函数是如何启动 Web 容器的?
在 SpringApplication.run 创建的上下文中,于 refresh 的 onRefresh 阶段调用 createWebServer 方法,通过 ServletWebServerFactory 创建并初始化 WebServer,底层创建线程池和监听端口的套接字。
Spring Boot 中如何将默认的 Tomcat 替换为 Jetty?
在 pom 文件中排除 spring-boot-starter-tomcat 依赖,并添加 spring-boot-starter-jetty 依赖即可。
Spring Boot 启动时在哪个阶段创建 Web 服务器?
在 AbstractApplicationContext 的 refresh 方法执行到 onRefresh 阶段时,由 ServletWebServerApplicationContext 重写的 onRefresh 方法调用 createWebServer 创建 Web 服务器。
如何有效阅读 Spring 源码来理解其原理?
采用带着问题去 debug 跟踪源码验证的方法,不要假定,要证明,通过实际调试来理解源码逻辑。