← 返回题目列表

Spring Boot 是怎么判断该不该启动 Web 服务器的?WebApplicationType 是什么?

中等 第 24 / 25 题 更新于 2026/07/28
WebApplicationTypeSpringApplicationWeb类型推断应用类型

简化版

**Spring Boot 启动时会「自动推断」这个应用是什么类型,从而决定要不要启动 Web 服务器、启动哪种——这就是 WebApplicationType。**它有三种值:SERVLET(传统的 Servlet Web 应用)——启动内嵌 Tomcat/Jetty/Undertow,创建 AnnotationConfigServletWebServerApplicationContextREACTIVE(响应式 Web 应用,WebFlux)——启动内嵌 Netty,创建响应式的 context;NONE(非 Web 应用)——不启动任何 Web 服务器,就是个普通程序(如定时任务、消息消费者、批处理),用普通的 context,跑完就退出(或靠非 web 的方式常驻)。怎么推断:Spring Boot 看 classpath 上有哪些类——如果有 WebFlux 相关类且没有 Spring MVC 的核心类,就是 REACTIVE;如果有 Servlet/Spring MVC 相关类,就是 SERVLET;两者都没有,就是 NONE可以手动指定new SpringApplication(App.class).setWebApplicationType(WebApplicationType.NONE) 或配置 spring.main.web-application-type=none,强制覆盖自动推断(比如引入了 web 依赖但就想跑个不带 web 的任务)。

详细版

三种 WebApplicationType

类型含义服务器ApplicationContext判断依据
SERVLET传统 Servlet WebTomcat/Jetty/UndertowServletWebServer…Contextclasspath 有 Servlet/Spring MVC
REACTIVE响应式 Web(WebFlux)NettyReactiveWebServer…Contextclasspath 有 WebFlux、无 MVC
NONE非 Web 应用不启动普通 AnnotationConfig…Context两者都没有

推断逻辑(简化)

① classpath 有 WebFlux 的 DispatcherHandler
   且 没有 Spring MVC 的 DispatcherServlet
   且 没有 Jersey → REACTIVE

② classpath 有 Servlet 类 和 Spring MVC 的 ConfigurableWebApplicationContext
   → SERVLET

③ 都不满足 → NONE
// 手动指定(覆盖自动推断)
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MyApp.class);
    app.setWebApplicationType(WebApplicationType.NONE);   // 强制非 web
    app.run(args);
}

// 或用 SpringApplicationBuilder
new SpringApplicationBuilder(MyApp.class)
    .web(WebApplicationType.NONE)
    .run(args);

// 或配置文件
// spring.main.web-application-type=none

⚠️ 一个经典场景:项目引入了 web 依赖(如 spring-boot-starter-web),但你其实想跑一个「不启动 Web 服务器」的任务(比如一个只做数据迁移、跑完就退出的程序,或一个纯消息消费者)。默认推断会因为 classpath 有 web 依赖而判定为 SERVLET、启动 Tomcat 占着端口——这时应显式 spring.main.web-application-type=nonesetWebApplicationType(NONE) 覆盖推断。反过来,同一个应用如果同时引入了 spring-boot-starter-web(MVC)和 spring-boot-starter-webflux推断会优先判定为 SERVLET(因为存在 MVC 的 DispatcherServlet),想用 WebFlux 就得显式指定 REACTIVE。所以「引了什么依赖」和「最终跑成什么类型」不总是一一对应,推断有优先级、也可被覆盖。

完整版教学

一、为什么需要推断应用类型

先理解「同样是 Spring Boot 应用,为什么要分类型」:

Spring Boot 应用其实有几种形态:
  ① 传统 Web 应用:接 HTTP 请求,需要启动 Tomcat 等 Web 服务器
  ② 响应式 Web 应用:用 WebFlux,需要启动 Netty(非阻塞)
  ③ 非 Web 应用:根本不接 HTTP——
     定时任务、消息消费者、批处理、命令行工具

不同形态,启动方式完全不同:
  - Web 应用要"启动 Web 服务器 + 用支持 Web 的 ApplicationContext"
  - 非 Web 应用"不启动服务器 + 用普通 ApplicationContext"

如果不区分:
  非 Web 应用也启动 Tomcat → 白占端口、白耗资源
  → 需要在启动时判断"我到底是哪种",走对应的启动流程

推断应用类型是因为「Spring Boot 应用有几种形态、启动方式不同」——传统 Web(启动 Tomcat)、响应式 Web(启动 Netty)、非 Web(不启动服务器,如定时任务/消息消费者/批处理)。不区分的话,非 Web 应用也启动 Tomcat 就白占端口白耗资源。所以启动时要判断「我是哪种」,走对应流程。理解「Spring Boot 应用分传统 Web/响应式 Web/非 Web 三形态、启动方式不同、需要推断类型走对应流程」,就理解了为什么要推断。

二、三种 WebApplicationType

三种类型各对应一种应用形态:

① SERVLET(传统 Servlet Web 应用):
   - 用 Spring MVC,基于 Servlet 规范(阻塞式)
   - 启动内嵌 Tomcat/Jetty/Undertow
   - context:ServletWebServerApplicationContext
   - 最常见(spring-boot-starter-web 引入的就是它)

② REACTIVE(响应式 Web 应用):
   - 用 Spring WebFlux,基于 Reactor(非阻塞、响应式)
   - 启动内嵌 Netty(默认)
   - context:ReactiveWebServerApplicationContext
   - spring-boot-starter-webflux 引入

③ NONE(非 Web 应用):
   - 不接 HTTP、不启动任何 Web 服务器
   - context:普通的 AnnotationConfigApplicationContext
   - 用于:定时任务、消息消费者、批处理、CLI 工具
   - run() 后如果没有非守护线程常驻,应用会跑完就退出

三种类型:SERVLET(传统 Spring MVC、阻塞式、启动 Tomcat/Jetty/Undertow,最常见)、REACTIVE(Spring WebFlux、非阻塞响应式、启动 Netty)、NONE(不接 HTTP、不启动服务器,用于定时任务/消息消费者/批处理/CLI,run 后无常驻线程就退出)。每种用不同的 ApplicationContext。理解「三类型:SERVLET(MVC/Tomcat/最常见)/REACTIVE(WebFlux/Netty/响应式)/NONE(不启动服务器/任务类应用)、各用不同 context」,就掌握了三种类型。

三、推断逻辑:看 classpath 有什么类

Spring Boot 怎么自动判断类型?靠「检查 classpath 上有哪些关键类」:

推断的核心:类是否存在(ClassUtils.isPresent 检查)

逻辑(简化,按顺序判断):
  ① 是不是 REACTIVE?
     classpath 有 WebFlux 的 DispatcherHandler(org.springframework.web.reactive.)
     且 没有 Spring MVC 的 DispatcherServlet
     且 没有 Jersey
     → 是 REACTIVE

  ② 是不是 SERVLET?
     classpath 有 Servlet 相关类 和 Spring MVC 的 web 上下文类
     → 是 SERVLET

  ③ 都不是 → NONE

原理:引入不同的 starter,会带来不同的类
  spring-boot-starter-web → 带来 Spring MVC 的 DispatcherServlet → 推断 SERVLET
  spring-boot-starter-webflux → 带来 WebFlux 的类(且不带 MVC)→ 推断 REACTIVE
  什么 web starter 都没引 → 两类关键类都没有 → 推断 NONE

所以"你引了什么 web 依赖",决定了推断结果

推断靠「检查 classpath 上有哪些关键类」(ClassUtils.isPresent):有 WebFlux 的 DispatcherHandler 且无 MVC 的 DispatcherServlet → REACTIVE;有 Servlet/MVC 的类 → SERVLET;都没有 → NONE。原理是「引入不同 starter 带来不同的类」——starter-web 带来 MVC 的 DispatcherServlet(推断 SERVLET)、starter-webflux 带来 WebFlux 类(推断 REACTIVE)、什么都没引则 NONE。理解「推断靠检查 classpath 关键类:有 WebFlux 无 MVC→REACTIVE、有 MVC→SERVLET、都没有→NONE、引什么 starter 决定推断结果」,就理解了自动推断的机制。

四、SERVLET 优先:MVC 和 WebFlux 都引入时

一个重要细节:同时引入 MVC 和 WebFlux 时的优先级:

如果 classpath 同时有 Spring MVC 和 Spring WebFlux:
  (比如项目里既引了 starter-web 又引了 starter-webflux)
  → 推断结果是 SERVLET(不是 REACTIVE)!

原因看推断逻辑:
  判断 REACTIVE 的条件之一是"没有 DispatcherServlet"
  但同时引了 MVC 就有 DispatcherServlet → REACTIVE 条件不满足
  → 落到 SERVLET

后果:
  你以为引了 WebFlux 就是响应式,结果因为也有 MVC,跑成了 SERVLET
  WebFlux 的响应式特性没生效(或行为混乱)

解决:
  ① 想用 WebFlux:别引 starter-web,或显式 setWebApplicationType(REACTIVE)
  ② 一般不建议同时引两个 web starter(容易混乱)

记忆:MVC 和 WebFlux 共存 → 默认 SERVLET 赢

一个重要细节:同时引入 Spring MVC 和 WebFlux 时,推断结果是 SERVLET(不是 REACTIVE)——因为判断 REACTIVE 的条件包含「没有 DispatcherServlet」,而引了 MVC 就有 DispatcherServlet,条件不满足,落到 SERVLET。后果是「以为引了 WebFlux 就响应式,结果跑成 SERVLET」。解决:想用 WebFlux 就别引 starter-web,或显式指定 REACTIVE。一般不建议同时引两个 web starter。理解「MVC 和 WebFlux 共存时默认 SERVLET 赢(因 REACTIVE 要求没有 DispatcherServlet)、想用 WebFlux 要显式指定或别引 MVC」,就掌握了这个易踩的坑。

五、手动指定:覆盖自动推断

自动推断可以被「手动指定」覆盖,这在几个场景很有用:

覆盖推断的方式:
  ① 代码:
     SpringApplication app = new SpringApplication(App.class);
     app.setWebApplicationType(WebApplicationType.NONE);
  ② Builder:
     new SpringApplicationBuilder(App.class).web(WebApplicationType.NONE).run(args);
  ③ 配置:spring.main.web-application-type=none

典型场景:
  场景一:引了 web 依赖,但想跑非 web 任务
    比如一个数据迁移工具,依赖里带了 starter-web(传递依赖或复用)
    但你就想跑完退出、不启动 Tomcat
    → 显式 none,避免白启动 Web 服务器占端口

  场景二:想强制用 REACTIVE
    classpath 同时有 MVC 和 WebFlux,默认推断成 SERVLET
    → 显式 reactive

  场景三:测试时不想启动 Web 服务器
    → none,让测试更快

所以:自动推断是"默认的便利",需要时可以精确控制

自动推断可被手动指定覆盖代码 setWebApplicationType、Builder .web(...)、配置 spring.main.web-application-type。典型场景:① 引了 web 依赖但想跑非 web 任务(数据迁移工具带了 web 传递依赖,但想跑完退出不启动 Tomcat,显式 none);② 强制 REACTIVE(MVC+WebFlux 共存默认 SERVLET 时);③ 测试时不启动服务器none 更快)。理解「手动指定覆盖推断(setWebApplicationType/spring.main.web-application-type)、场景:引了 web 依赖想跑非 web 任务用 none、强制 reactive、测试用 none」,就掌握了覆盖推断的用法。

六、SpringApplication 的其他定制

推断类型只是 SpringApplication 众多可定制项之一,顺带了解其他定制:

SpringApplication 启动前可定制(在 run 之前):
  app.setWebApplicationType(...)      设应用类型
  app.setBannerMode(Banner.Mode.OFF)  关闭启动横幅
  app.setDefaultProperties(map)        设默认属性(最低优先级)
  app.setAdditionalProfiles("dev")     追加激活的 profile
  app.addListeners(...)                加事件监听器(监听早期事件)
  app.addInitializers(...)             加 ApplicationContextInitializer
  app.setLogStartupInfo(false)         不打印启动信息
  app.setLazyInitialization(true)      全局懒加载(加快启动)

或用 SpringApplicationBuilder(链式,适合复杂/父子容器):
  new SpringApplicationBuilder(App.class)
    .web(WebApplicationType.NONE)
    .bannerMode(Banner.Mode.OFF)
    .profiles("dev")
    .run(args);

这些定制都在"启动之前"设置,改变 SpringApplication.run() 的行为

setWebApplicationType 只是 SpringApplication 众多定制项之一——启动前(run 之前)还能:setBannerMode(关横幅)、setDefaultProperties(默认属性)、setAdditionalProfiles(追加 profile)、addListeners(监听早期事件,见启动事件题)、setLazyInitialization(全局懒加载加快启动)等,或用 SpringApplicationBuilder 链式定制(适合父子容器)。这些都在启动前设置以改变 run() 行为。理解「SpringApplication 启动前可定制:setWebApplicationType/setBannerMode/setAdditionalProfiles/addListeners/setLazyInitialization、或用 SpringApplicationBuilder 链式」,就把 WebApplicationType 放进了 SpringApplication 定制的整体图景。

记忆钩子:「Spring Boot 启动推断 WebApplicationType 决定要不要启动 Web 服务器:①SERVLET(传统 MVC/阻塞/启动 Tomcat,最常见)②REACTIVE(WebFlux/非阻塞/启动 Netty)③NONE(不启动服务器,定时任务/消息消费/批处理);推断靠检查 classpath 关键类:有 WebFlux 的 DispatcherHandler 且无 MVC 的 DispatcherServlet→REACTIVE、有 MVC→SERVLET、都没有→NONE;★MVC 和 WebFlux 共存默认 SERVLET 赢(REACTIVE 要求没 DispatcherServlet);可手动覆盖 setWebApplicationType/spring.main.web-application-type=none(引了 web 依赖但想跑非 web 任务时用)」

七、常见误区与追问

  • 误区:Spring Boot 应用一定会启动 Web 服务器。 不一定——WebApplicationType 为 NONE 时不启动任何 Web 服务器(用于定时任务、消息消费者、批处理、CLI 工具);是否启动取决于推断结果或手动指定。
  • 误区:引入了 spring-boot-starter-web 就没法不启动 Tomcat。 能——显式设 spring.main.web-application-type=none 或 setWebApplicationType(NONE) 覆盖推断,即使 classpath 有 web 依赖也不启动 Web 服务器;适合「引了 web 依赖但想跑非 web 任务」的场景。
  • 误区:同时引 MVC 和 WebFlux 会用 WebFlux(响应式)。 默认推断成 SERVLET——因为判断 REACTIVE 的条件包含「没有 DispatcherServlet」,引了 MVC 就有 DispatcherServlet、条件不满足、落到 SERVLET;想用 WebFlux 要显式指定 REACTIVE 或不引 MVC。
  • 误区:WebApplicationType 是运行时动态变的。 是启动时(SpringApplication.run 早期)根据 classpath 一次性推断确定的,之后不变;它决定了创建哪种 ApplicationContext、启动哪种服务器。
  • 追问:Spring Boot 怎么知道该启动 Tomcat 还是 Netty? 靠 WebApplicationType 推断——检查 classpath:有 Spring MVC(DispatcherServlet)就是 SERVLET,启动 Servlet 容器(Tomcat/Jetty/Undertow);有 WebFlux(DispatcherHandler)且无 MVC 就是 REACTIVE,启动 Netty;据此创建对应的 ApplicationContext 和 Web 服务器。
  • 追问:一个纯消息消费者/定时任务应用,WebApplicationType 应该是什么? NONE——它不接 HTTP、不需要 Web 服务器;如果因为传递依赖带了 web starter 被推断成 SERVLET,会白启动 Tomcat 占端口,应显式设 spring.main.web-application-type=none;应用靠消息监听容器/调度线程等非守护线程常驻,不会跑完就退出。
  • 追问:除了 WebApplicationType,SpringApplication 启动前还能定制什么? 很多——setBannerMode(横幅)、setAdditionalProfiles(追加 profile)、setDefaultProperties(默认属性)、addListeners(早期事件监听)、addInitializers、setLazyInitialization(全局懒加载加快启动)、setLogStartupInfo 等,都在 run() 前设置,或用 SpringApplicationBuilder 链式配置。

八、加强记忆

Spring Boot 启动时自动推断 WebApplicationType,决定要不要启动 Web 服务器、启动哪种。三种:SERVLET(传统 Spring MVC、阻塞式、启动内嵌 Tomcat/Jetty/Undertow,用 ServletWebServerApplicationContext,最常见);REACTIVE(Spring WebFlux、非阻塞响应式、启动内嵌 Netty,用 ReactiveWebServerApplicationContext);NONE(不接 HTTP、不启动 Web 服务器,用普通 context,用于定时任务/消息消费者/批处理/CLI)。推断靠检查 classpath 关键类:有 WebFlux 的 DispatcherHandler MVC 的 DispatcherServletREACTIVE;有 Servlet/MVC 类 → SERVLET;都没有 → NONE(引什么 web starter 决定推断结果)。关键细节:同时引 MVC 和 WebFlux 时默认 SERVLET(因为 REACTIVE 要求「没有 DispatcherServlet」)。可手动覆盖setWebApplicationType(NONE)spring.main.web-application-type=none——典型场景是「引了 web 依赖但想跑不启动服务器的任务」(数据迁移、纯消息消费者)。WebApplicationTypeSpringApplication 众多启动前定制项之一(还有 setBannerMode/setAdditionalProfiles/addListeners/setLazyInitialization 等)。一句话「WebApplicationType 决定要不要启动 Web 服务器:SERVLET(MVC/Tomcat/最常见)/REACTIVE(WebFlux/Netty)/NONE(不启动,任务类);靠 classpath 关键类推断(有 WebFlux 无 MVC→REACTIVE、有 MVC→SERVLET、都没有→NONE),MVC+WebFlux 共存默认 SERVLET;可 setWebApplicationType/spring.main.web-application-type=none 覆盖」。