WebFlux 两套编程模型底层都是基于 Reactor‑Netty 非阻塞,性能上限理论几乎一样,不存在某一个天生更快;差异在编程范式、代码组织、灵活性、适用场景。
底层共同点:
- 都运行在 Reactor Netty 非阻塞容器
- 都返回
Mono<T> / Flux<T>- 都不能写阻塞代码(JDBC、RestTemplate、sleep)
- 都支持 SSE /ndjson/ 普通 json
1. 代码直观对比
① @RestController(注解式,命令式 / 注解驱动)
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public Flux<User> list() {
return userService.findAll();
}
@GetMapping("/{id}")
public Mono<User> getById(@PathVariable Long id) {
return userService.findById(id);
}
}
② Functional Routing 函数式路由(RouterFunction + HandlerFunction)
分为两部分:HandlerFunction(业务处理) + RouterFunction(路由定义)
// Handler:业务逻辑,等价于Controller的方法
@Component
public class UserHandler {
private final UserService userService;
public UserHandler(UserService userService) { this.userService = userService; }
public Mono<ServerResponse> list(ServerRequest request) {
Flux<User> flux = userService.findAll();
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(flux, User.class);
}
public Mono<ServerResponse> getById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
// RouterFunction 路由注册,替代 @RequestMapping/@GetMapping
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler userHandler) {
return RouterFunctions.route()
.GET("/api/user", userHandler::list)
.GET("/api/user/{id}", userHandler::getById)
.build();
}
}
关键点:
- HandlerFunction 签名:
Mono<ServerResponse> handle(ServerRequest request)ServerRequest:封装请求(path、param、header、body)ServerResponse:构建响应(status、header、body)
2. 核心区别对比表
| 对比项 | @RestController 注解式 | Functional Routing 函数式路由 |
|---|---|---|
| 编程范式 | 注解驱动,类 + 方法注解,类似 SpringMVC | 函数式,RouterFunction + HandlerFunction,lambda / 方法引用 |
| 请求入参 | 注解绑定:@PathVariable @RequestParam @RequestBody | 手动从ServerRequest提取,无注解绑定 |
| 响应输出 | 直接返回 Mono<T> / Flux<T>,框架自动包装 | 手动构建ServerResponse.ok()…body(),自由度高 |
| 路由注册 | 注解扫描自动发现 | 手动构造 RouterFunction Bean,显式定义路由表 |
| AOP / 拦截 | 标准 Spring AOP、@ExceptionHandler、@ControllerAdvice | 优先用 filter() / before() / after();@ControllerAdvice对函数路由不生效 |
| 全局异常处理 | @ControllerAdvice + @ExceptionHandler | 需要自定义 WebExceptionHandler |
| 请求 body 解析 | @RequestBody Mono<User> 自动解析 | request.bodyToMono(User.class)手动调用 |
| 可读性 | 对 MVC 开发者非常友好,上手快 | 代码偏底层,新手上手成本高 |
| 动态路由 | 很难运行时动态增删接口 | 原生支持运行时动态组装 RouterFunction,动态增删路由 |
| 性能理论 | Reactor‑Netty,非阻塞 | Reactor‑Netty,非阻塞,理论性能一致 |
⚠️重点坑:
@ControllerAdvice不会作用于 Functional Routing! 函数式路由全局异常必须实现WebExceptionHandler,不能用注解的全局异常。
3. 性能:哪个效率更高?
绝大多数业务场景,两者性能几乎无差别,不要迷信函数式更快。
- 底层都是 Reactor‑Netty + Reactor 流处理;
- @RestController 内部也是把注解元数据翻译成内部路由元信息;函数式是手动构建路由元信息;
- 微小差异:
- Functional Routing:少了注解解析、参数解析的反射绑定逻辑;理论上微小的 CPU 优势,微乎其微,业务层消耗远大于这点开销。
- @RestController:参数绑定用反射 + 注解,有极少量开销,但业务接口几乎感知不到。
✅ 性能瓶颈永远是:数据库 IO、外部 HTTP 调用,不是这两套模型的选择。
只有网关类场景,大量短请求、超高 QPS,函数式的微小优势才会体现;普通业务接口,测不出差距。
4. 优缺点详细分析
✅ @RestController(注解式 WebFlux)
优点
- 学习成本低,从 SpringMVC 迁移过来几乎无缝;注解
@Get/PostMapping、@PathVariable习惯完全继承。 - 参数绑定自动化,不用手动写
request.pathVariable()、request.bodyToMono()。 - AOP、
@ControllerAdvice全局异常、校验@Valid、@RequestBody校验全部开箱即用。 - 代码简洁,业务代码量少,日常 CRUD 业务开发效率高。
缺点
- 路由是注解扫描自动生成,运行时动态修改路由很麻烦,不适合动态接口场景。
- 请求、响应被框架封装,底层请求响应对象拿出来不够直接。
- 注解魔法,路由分散在各个 Controller 类,全局看所有接口需要扫描全部源码。
适合场景:普通业务后端,CRUD,团队大部分熟悉 SpringMVC;绝大多数业务 WebFlux 项目推荐这个。
✅ Functional Routing 函数式路由
优点
- 路由集中管理:全部接口在 RouterFunction Bean 一处集中定义,一目了然。
- 运行时动态路由:可以运行时拼接、修改 RouterFunction,适合网关、动态 API 场景。
- 请求响应完全可控:
ServerRequest / ServerResponse,可以精细控制 header、status、body;没有注解黑盒。 - 可以链式加 filter,针对某一组路由做拦截器,粒度可以到单个路由。
// 给一组路由单独加filter
route().path("/api/internal", b -> b
.GET("/info", handler::info)
).filter((req, next) -> {
// 这组接口专属拦截逻辑
return next.handle(req);
})
缺点
- 上手门槛高,要熟悉
ServerRequest/ServerResponse;每个参数、body 都要手动解析。 - @ControllerAdvice、@Valid 全局校验不生效,异常处理、参数校验需要自己封装。
- 样板代码变多:每次都要写
ServerResponse.ok().body(...),写 CRUD 会啰嗦。 - AOP 切面很难切入 HandlerFunction,不能直接用
@Around切 handler 方法。
适合场景
- 自建网关、内部代理服务;
- 需要运行时动态增删路由;
- 需要细粒度给不同路由分组加 filter;
- 追求极致底层控制,接口数量不多。
5. 选型建议
- 普通业务系统(CRUD、后台服务)👉优先 @RestController
开发效率高,团队维护成本低,异常、校验、AOP 全部原生支持,性能足够。
- 做网关、反向代理、动态接口平台👉优先 Functional Routing
集中路由、动态组装、细粒度 filter 是它的强项。
- 可以混合使用!同一个项目可以同时存在 @RestController 和 RouterFunction,互不冲突。
6. 常见踩坑
- 函数路由不要指望
@ControllerAdvice,必须实现WebExceptionHandler做全局异常; @Valid校验在 HandlerFunction 不会自动触发,需要手动调用校验器;- 不要误以为函数式就一定更快,业务系统几乎感知不到性能差异;
- 无论哪种模型,都禁止写阻塞代码(Thread.sleep、同步 JDBC),否则非阻塞优势全部丧失。
补充小示例:函数路由全局 filter
@Bean
public RouterFunction<ServerResponse> routes(UserHandler handler) {
return RouterFunctions.route()
.GET("/api/user", handler::list)
.GET("/api/user/{id}", handler::getById)
.filter((request, next) -> {
// 全局日志、鉴权
System.out.println("path:" + request.path());
return next.handle(request);
})
.build();
}
##########################
附:RouterFunction<ServerResponse> 与 RouterFunctionMapping
结论:
- 如果你把
RouterFunction<ServerResponse>注册为 Spring Bean,RouterFunctionMapping会自动发现、收集、合并所有 RouterFunction Bean,自动注册为路由映射,不需要手动注册 RouterFunctionMapping。 - Spring WebFlux 自动配置
WebFluxAutoConfiguration内部会创建RouterFunctionMapping,它会从容器查找全部RouterFunction<?>bean,做组合RouterFunctions.and()。 - 也可以手动构建
RouterFunctionMappingBean(高级场景,覆盖自动行为)。
注意:这是 WebFlux 函数式编程模式,和
@Controller/@RequestMapping两套体系并存,可以混合使用。
1. 最简示例:把 RouterFunction 声明为 @Bean(自动被 RouterFunctionMapping 拾取)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
@Configuration
public class MyRouteConfig {
// ✅ 直接返回 RouterFunction<ServerResponse> Bean,自动被 RouterFunctionMapping 收集
@Bean
public RouterFunction<ServerResponse> helloRoute() {
return route(GET("/hello"),
request -> ServerResponse.ok().bodyValue("hello webflux router function"));
}
// 多个 RouterFunction Bean,会被自动合并
@Bean
public RouterFunction<ServerResponse> userRoute() {
return route(GET("/user/{id}"), request -> {
String id = request.pathVariable("id");
return ServerResponse.ok().bodyValue("userId:" + id);
});
}
}
- 启动后访问
/hello、/user/123直接生效。 RouterFunctionMapping内部会拿到上下文所有RouterFunction,执行router1.and(router2).and(router3)...。
2. 手动构造 RouterFunctionMapping Bean(接管,不使用自动收集)
如果你不想 Spring 自动收集所有 RouterFunction Bean,可以自己定义
RouterFunctionMapping,传入你手动组装好的 RouterFunction。
@Bean
public RouterFunctionMapping routerFunctionMapping(RouterFunction<ServerResponse> combinedRouter) {
RouterFunctionMapping mapping = new RouterFunctionMapping(combinedRouter);
// 可选:设置顺序,和 @Controller 映射做优先级控制
mapping.setOrder(-1);
return mapping;
}
// 手动合并,只有这里的路由生效,容器里其他 RouterFunction Bean 会被忽略!
@Bean
public RouterFunction<ServerResponse> combinedRouter() {
return route(GET("/manual"), req -> ServerResponse.ok().bodyValue("manual mapping"));
}
⚠️关键点:一旦你自己实例化
new RouterFunctionMapping(myRouter),它不会再自动去 Spring 容器捞其他 RouterFunction Bean,只使用你传入的那一个。
3. RouterFunctions.route () 常用完整示例(POST + json body)
@Bean
public RouterFunction<ServerResponse> orderRouter() {
return route()
.GET("/order/{orderId}", request -> ServerResponse.ok().bodyValue("order:" + request.pathVariable("orderId")))
.POST("/order", request ->
request.bodyToMono(Order.class)
.flatMap(order -> ServerResponse.ok().bodyValue("receive:" + order.getName()))
)
.build();
}
static class Order {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
4. 关键底层细节(RouterFunctionMapping)
RouterFunctionMapping 源码逻辑简述:
// Spring WebFlux 自动配置逻辑伪代码
Collection<RouterFunction<?>> routerFunctions = context.getBeansOfType(RouterFunction.class).values();
RouterFunction<?> composite = routerFunctions.stream()
.reduce(RouterFunction::and)
.orElse(null);
return new RouterFunctionMapping(composite);
- 容器有多个 RouterFunction bean → 自动
.and()合并为一个复合路由。 - 没有任何 RouterFunction Bean:
RouterFunctionMapping依然存在,只是无路由规则。
5. 容易踩坑点
- ❌ 不要只写
RouterFunction<ServerResponse> fn = route(...);不交给 Spring 管理(局部变量,不是 Bean,RouterFunctionMapping 看不到,不会生效)。 - 函数式路由
RouterFunction和注解式@Controller可以共存,DispatcherHandler 会分别调用RequestMappingHandlerMapping、RouterFunctionMapping。 - 自己 new
RouterFunctionMapping传入自定义路由时,不再自动收集容器其他 RouterFunction。 - 过滤器:
.filter()在 RouterFunction 层面做;全局过滤器用WebFilter。
6. 增加 filter 示例
@Bean
public RouterFunction<ServerResponse> filteredRoute() {
return route(GET("/demo"), req -> ServerResponse.ok().bodyValue("demo"))
.filter((request, next) -> {
System.out.println("request path:" + request.path());
return next.handle(request);
});
}
总结记忆
- ✅
@Bean RouterFunction<ServerResponse>→ 自动被 RouterFunctionMapping 收集合并,开箱即用。 - ✅ 多个 RouterFunction Bean,自动 and () 合并。
- ⚠️ 手动 new
RouterFunctionMapping(xxx),只使用你传入的路由,不再自动扫描容器其他 RouterFunction。

654

被折叠的 条评论
为什么被折叠?



