Boot41 MVC怎么选
Spring Boot 4.1.2 的官方“第一个应用”教程,在 Maven 依赖里放的是 org.springframework.boot:spring-boot-starter-webmvc。这不表示 WebMVC 有什么新功能,也不等于 WebFlux 已经过时。WebMVC 本来就是 Spring 的 Servlet 栈;教程传递的只是一个工程信号:多数 HTTP 服务,先从同步、阻塞、容易诊断的调用链出发,成本更低。
选型的分界不在 Controller 有没有 @GetMapping,也不在返回类型是否写成 Mono,而在一次请求是否能保持同一种并发模型。把 JDBC、阻塞式 SDK 和同步 HTTP 客户端塞进 Reactor 链路,只是把阻塞藏进事件循环风险里;给普通后台接口强行引入响应式类型,也不会减少数据库等待。
教程里的 starter,先解决的是默认路径
官方教程使用 Boot 4.1.2、Java 17,并为 Web 应用加入 spring-boot-starter-webmvc。它走 Servlet 运行模型。对订单查询、运营后台、企业 API 来说,这个路径贴近依赖现实:JPA/JDBC 是阻塞式,鉴权过滤器和第三方客户端也常以同步方式工作。线程会在 I/O 等待期间占住,但调用栈、事务边界和异常处理仍很熟悉。
这不是性能结论。并发量、下游延迟、连接池都会改变结果,不能脱离压测给出谁“快几倍”的答案。业务依赖本来阻塞时,MVC 不必再包一层调度与背压语义。
一段足够小的 Boot 4.1.2 MVC 应用
下面是可独立编译的 Maven 示例,重点是确认 spring-boot-starter-webmvc、启动类和同步 Controller 的组合。
<!-- pom.xml -->
<projectxmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot41-mvc-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties><java.version>17</java.version></properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
</dependencies>
</project>
// src/main/java/com/example/mvc/DemoApplication.java
packagecom.example.mvc;
importjava.time.Instant;
importjava.util.Map;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestParam;
importorg.springframework.web.bind.annotation.RestController;
@SpringBootApplication
publicclass DemoApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(DemoApplication.class,args);
}
}
@RestController
@RequestMapping("/api")
class GreetingController{
@GetMapping("/greeting")
Map<String,String>greeting(@RequestParam(defaultValue="guest")Stringname){
returnMap.of("message","hello, "+name,"at",Instant.now().toString());
}
}
执行 mvn package 后运行 java -jar target/boot41-mvc-demo-0.0.1-SNAPSHOT.jar,访问 /api/greeting?name=spring 会得到 JSON。这个例子没有什么“反应式替代品”可炫耀,恰恰说明选 MVC 时不必为简单请求额外制造抽象。
看依赖链,不要只看接口吞吐
如果请求大部分时间在等待 MySQL、Redis 同步驱动,或调用同步的支付、存储、风控 SDK,MVC 往往是诚实的选择。先把连接池、超时、隔离和慢查询做好;把 Controller 改成 Mono 不会让阻塞驱动变成非阻塞。WebFlux 可将阻塞调用移到 boundedElastic 等调度器,避免堵住事件循环,但这只是兼容策略。
另一类服务完全不同。长连接推送、SSE、流式数据转发、网关型聚合,或者高并发 I/O 场景中下游同时提供 R2DBC、响应式消息客户端和 WebClient,请求链可以从入口到存储都保持非阻塞。这时 WebFlux 的背压语义和少量事件循环线程才有施展空间。官方文档也明确指出,WebFlux 不依赖 Servlet API,是全异步、非阻塞的,并通过 Reactor 实现 Reactive Streams 规范。
常被忽略的是团队的运行能力。WebFlux 不是“把 List 改成 Flux”那么简单:日志上下文传播、链路追踪、超时取消、错误恢复和测试方式都会变。若值班同学看到一条 Reactor 栈还无法快速定位下游阻塞点,那么在业务规模尚未证明需要之前,MVC 的可维护性本身就是架构收益。
已有 MVC 服务,不必做全量翻修
迁移最容易犯的错误,是把 MVC 项目按包名或模块一次性改成 WebFlux。Servlet API、同步拦截器、JPA 事务、阻塞式 Feign 或 SDK 都会让改造面迅速扩大,而混用两套心智模型会让事故更难复盘。
更稳妥的做法是按边界拆:保留核心交易与后台接口的 MVC;把需要流式转发或大量长连接的能力做成独立 WebFlux 服务,用 HTTP、消息或网关连接。这样能单独压测,也能缩小背压或取消语义问题的范围。若只是异步调用外部接口,MVC 应用也可以使用 WebClient;客户端异步不要求服务端改成 WebFlux。
新项目可以用一张很短的判断表开始讨论,而不是由“更现代”决定:
| 真实约束 |
更自然的起点 |
| JPA/JDBC、同步 SDK、传统 Servlet 过滤器占主导 |
WebMVC |
| 端到端 Reactor/R2DBC、流式响应、SSE 或大量长连接 |
WebFlux |
| 只有少数边缘接口需要流式或非阻塞 |
MVC 主体,边界处单独引入或拆分 WebFlux |
| 只是担心未来流量会上升 |
先测下游、连接池和容量,再决定 |
Boot 4.1.2 教程里的 spring-boot-starter-webmvc 值得注意,不是因为 MVC “回来了”,而是它提醒开发者:框架依赖应当服从现有 I/O 事实。选 MVC 还是 WebFlux,最后不是语法偏好,而是要让线程模型、驱动能力、故障处理方式和团队经验说同一种语言。
参考资料
- Spring Boot 4.1.2:Developing Your First Spring Boot Application
- Spring Boot Reference:Servlet Web Applications
- Spring Boot Reference:Reactive Web Applications