Skip to content

Spring Boot 是如何通过 main 方法启动 web 项目的? ​

  • 标签:Spring Boot、后端

🏛 业务架构师 ​

从 main 启动 web 项目的关键在于内嵌容器改变了部署形态:

  1. SpringApplication.run(App.class, args) 启动 Spring 上下文;
  2. 应用类型推断为 Servlet,** Tomcat 以 jar 内依赖的形式被实例化**(TomcatServletWebServerFactory);
  3. refresh 过程的 onRefresh 阶段创建 Tomcat、注册端口、把 DispatcherServlet 绑定到 ServletContext;
  4. 容器启动完成、Spring 就绪、ApplicationRunner 执行预热,服务开始接收请求。

业务价值我讲三点:

  • 部署模型从"应用装进服务器"变成"服务器长在应用里":以前运维先装 Tomcat、配 context、放 war,环境差异是无数深夜故障的来源;现在一个 fat jar java -jar 就跑——环境一致性从流程约定变成物理事实,"我本地好的"这类争论大幅减少;
  • 它是容器化与弹性伸缩的前提:镜像里只需要 JRE + jar,镜像构建标准化、启动命令统一,K8s 的滚动发布、Serverless 的快速拉起都建立在这个简单性上——没有内嵌容器,就没有今天 Java 的云原生故事;
  • 端口与优雅停机的责任内移:management 端口分离、优雅停机(先摘流量、处理完存量请求再退出)由应用自己实现,配合 K8s preStop + readiness 探针做无损发布。

提醒一个坑:内嵌 Tomcat 默认参数偏保守(maxThreads 200),大流量前要按业务画像调参并压测——部署形态变简单了,容量设计的责任一点没少。

🏗 系统架构师 ​

main 方法能拉起 web 服务,关键在于 Spring Boot 的内嵌容器 + 自动装配让"应用"与"容器"合为一个进程:

  1. SpringApplication.run() 推断应用类型:classpath 检测到 servlet 相关类(如 tomcat-embed)判定为 Servlet Web 应用;
  2. 创建对应的 AnnotationConfigServletWebServerApplicationContext;
  3. refresh() 过程中走到 onRefresh()——Web 场景重写为 createWebServer():通过 ServletWebServerFactory(自动配置根据依赖选定 TomcatFactory)new 出内嵌 Tomcat 实例,注册 DispatcherServlet 到 ServletContext,初始化端口绑定(这一步发生在 refresh 内,容器还没完全就绪,所以早期版本要用 ApplicationContextInitializer/事件拿到 WebServer);
  4. finishRefresh() 发布 ready 事件,Runner 执行,main 线程虽随后退出,但 Tomcat 的非 daemon 线程持有着进程,JVM 不退出,服务持续运行。

与传统 war 部署的本质区别:控制权反转——以前是"Tomcat 启动并加载你的应用",现在是"你的应用启动并拉起 Tomcat"。收益:java -jar 一条命令部署、容器无关性(可换 Jetty/Undertow 一个依赖搞定)、DevTools 热重启成为可能;这也是 Spring Boot 适配容器化(一个镜像一个进程)的根基。

💻 全栈开发专家 ​

为什么 main 方法就能起 web 项目:SpringBoot 把 Tomcat 打进 jar 里当普通对象创建,而不是把应用部署到外部容器——传统是"应用适配容器",现在是"容器成为应用的一部分"。

流程:

  1. SpringApplication.run() 推断应用类型:classpath 有 DispatcherServlet → SERVLET(servlet web 应用),有 WebFlux → REACTIVE。
  2. 据此创建对应的 ApplicationContext:AnnotationConfigServletWebServerApplicationContext。
  3. refresh 的 onRefresh() 阶段调用 createWebServer():通过 ServletWebServerFactory(自动配置按依赖选定 Tomcat/Jetty/Undertow)new 出内嵌 Tomcat 并注册 DispatcherServlet。
  4. finishRefresh() 发布 WebServerInitializedEvent,webserver 正式 start,端口绑定,开始接客。
  5. 主线程实际不阻塞,应用靠非 daemon 的 Tomcat 线程保持存活。

工程价值与实操:

  • 打成 fat jar java -jar app.jar 独立运行,容器化天然友好(一个 jar 一个进程);
  • 一个机器可以多实例不同端口,本地起两个服务互调很方便;
  • 定制化:server.port、server.tomcat.threads.max 直接配置;换容器只需换 starter 依赖(排除 tomcat 引 jetty);
  • 坑:fat jar 里 JSP 支持差;部署 WAR 到外部 Tomcat 时要继承 SpringBootServletInitializer,初始化器入口和内嵌模式不同。