概述

Spring WebFlux 是 Spring 5 引入的响应式编程框架,与传统的 WebMVC 形成鲜明对比。本文通过组件架构、执行流程、代码示例等多维度对比两者的核心差异。

核心对比:

  • 🏗️ 架构组件差异
  • ⚡ 执行模型对比
  • 💻 编程范式转变
  • 🚀 性能特性分析

适用场景:

  • WebMVC:传统同步阻塞应用
  • WebFlux:高并发、IO密集型应用

WebMVC vs WebFlux 架构对比

组件架构

核心组件对比:

组件层WebMVCWebFlux说明
Web层Spring MVCSpring WebFluxWeb框架
服务器Servlet容器(Tomcat等)Netty/Undertow底层服务器
APIServlet APIReactive Streams编程接口
线程模型一请求一线程事件循环并发模型

执行流程

WebMVC 执行过程

特点:

1
2
请求 → DispatcherServlet → Handler Mapping → Controller
← 同步阻塞等待 ←

执行特征:

  • 🔒 同步阻塞:线程等待IO完成
  • 🧵 一请求一线程:高并发时线程数激增
  • ⏱️ 资源占用:等待期间线程被占用

WebFlux 执行过程

特点:

1
2
3
4
请求 → DispatcherHandler → Handler Mapping → Handler
← 立即返回Mono/Flux ←

订阅后异步执行

执行特征:

  • 异步非阻塞:线程立即返回
  • 🔄 事件驱动:基于Reactive Streams
  • 💪 高并发:少量线程处理大量请求

代码写法对比

场景:返回视图

WebMVC 写法

典型代码:

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class WebMvcController {

@GetMapping("/hello")
public String hello(Model model) {
// 同步阻塞调用
String data = service.getData();
model.addAttribute("data", data);
return "hello"; // 返回视图名
}
}

特点:

  • ✅ 简单直观
  • ✅ 同步代码易理解
  • ❌ 阻塞线程

WebFlux 路由式写法

路由配置:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class RouterConfig {

@Bean
public RouterFunction<ServerResponse> route(HelloHandler handler) {
return RouterFunctions
.route(GET("/hello").and(accept(TEXT_HTML)),
handler::hello);
}
}

Handler实现:

1
2
3
4
5
6
7
8
9
10
@Component
public class HelloHandler {

public Mono<ServerResponse> hello(ServerRequest request) {
Mono<String> data = service.getData(); // 异步获取
return ServerResponse
.ok()
.render("hello", Map.of("data", data));
}
}

WebFlux 注解式写法(兼容WebMVC):

1
2
3
4
5
6
7
8
9
@RestController
public class WebFluxController {

@GetMapping("/hello")
public Mono<String> hello() {
// 返回响应式类型
return service.getData();
}
}

写法对比:

特性WebMVCWebFlux路由式WebFlux注解式
语法简单函数式类似WebMVC
灵活性一般一般
迁移成本-
适用场景传统应用新项目平滑迁移

编程思想对比

命令式编程(WebMVC)

定义:

1
a = b + c

含义: a 的值由 b 和 c 计算得出,后续 b、c 变化不影响 a

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 辅助方法:模拟耗时操作
private String createStr() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "some string";
}

// WebMVC 命令式写法
private String get1() {
log.info("get1 start");
String result = createStr(); // 阻塞5秒
log.info("get1 end.");
return result;
}

执行日志:

分析:

  • ⏱️ "get1 start" 和 "get1 end" 相隔 5秒
  • 🔒 线程在 createStr() 执行期间被阻塞
  • 📊 一个请求占用一个线程 5 秒

响应式编程(WebFlux)

定义:

1
a := b + c

含义: a 的值由 b 和 c 计算得出,后续 b、c 变化会影响 a(变化传递)

代码示例:

1
2
3
4
5
6
7
// WebFlux 响应式写法
private Mono<String> get2() {
log.info("get2 start");
Mono<String> result = Mono.fromSupplier(() -> createStr());
log.info("get2 end.");
return result; // 立即返回,不阻塞
}

执行日志:

分析:

  • ⚡ "get2 start" 和 "get2 end" 几乎同时
  • 🚀 线程立即返回,不等待 createStr() 完成
  • ♻️ createStr()订阅时异步执行

核心差异总结

性能对比

维度WebMVCWebFlux
线程模型一请求一线程事件循环(少量线程)
并发能力受线程数限制可处理海量并发
资源消耗高(线程切换开销)低(固定线程数)
适用场景CPU密集型IO密集型
响应延迟取决于业务逻辑更低(非阻塞)

实际效果

从调用者(浏览器)角度:

  • ⏱️ 两者响应时间相同(都是5秒)
  • 👀 用户体验无明显差异

从服务端角度:

1
2
3
4
5
6
7
8
9
10
WebMVC:
线程1 ████████████████████ (阻塞5秒)
线程2 ████████████████████ (阻塞5秒)
线程3 ████████████████████ (阻塞5秒)
...
线程N ████████████████████ (阻塞5秒)

WebFlux:
线程1 ▓░░░░░░░░░░░░░░░░▓ (立即返回,处理多个请求)
线程2 ▓░░░░░░░░░░░░░░░░▓ (立即返回,处理多个请求)

WebFlux 核心优势:

💡 能够以固定的线程数处理高并发,充分发挥机器性能。

优缺点对比

WebMVC:

优点缺点
✅ 编程模型简单❌ 高并发时线程数激增
✅ 调试容易❌ 资源消耗大
✅ 生态成熟❌ 阻塞等待浪费资源
✅ 学习成本低❌ 扩展性受限

WebFlux:

优点缺点
✅ 高并发处理能力强❌ 学习曲线陡峭
✅ 资源利用率高❌ 调试困难
✅ 适合IO密集型❌ 生态不够成熟
✅ 固定线程数❌ CPU密集型无优势

选择建议

使用 WebMVC 的场景

推荐使用:

  • ✅ 传统CRUD应用
  • ✅ 内部管理系统
  • ✅ 并发量不高(< 1000 QPS)
  • ✅ 团队不熟悉响应式编程
  • ✅ CPU密集型计算

使用 WebFlux 的场景

推荐使用:

  • ✅ 高并发API网关
  • ✅ 实时数据流处理
  • ✅ 微服务间通信
  • ✅ IO密集型应用
  • ✅ 需要背压控制的场景

迁移建议

平滑迁移路径:

1
2
3
4
5
Step 1: 使用WebFlux注解式写法(兼容WebMVC)

Step 2: 逐步引入Mono/Flux

Step 3: 改造成函数式路由(可选)

注意事项:

  • ⚠️ 确保所有依赖都支持响应式
  • ⚠️ 避免在WebFlux中使用阻塞操作
  • ⚠️ 充分测试并发场景
  • ⚠️ 团队需要响应式编程培训

最佳实践

WebFlux 开发建议

1. 避免阻塞操作

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 错误:在WebFlux中使用阻塞操作
@GetMapping("/bad")
public Mono<String> bad() {
String data = blockingService.getData(); // 阻塞!
return Mono.just(data);
}

// ✅ 正确:使用响应式API
@GetMapping("/good")
public Mono<String> good() {
return reactiveService.getData(); // 非阻塞
}

2. 使用响应式数据库

1
2
3
4
5
6
7
// 推荐:R2DBC(响应式关系数据库)
@Autowired
private R2dbcEntityTemplate template;

public Flux<User> findAll() {
return template.select(User.class).all();
}

3. 错误处理

1
2
3
4
5
6
7
public Mono<String> getData() {
return service.getData()
.onErrorResume(error -> {
log.error("Error occurred", error);
return Mono.just("default value");
});
}

参考资源

学习资料:

相关技术:

  • Reactive Streams
  • Project Reactor
  • RxJava