 vs @RestController 注解式)
WebFlux 两套编程模型底层都是基于 Reactor‑Netty 非阻塞性能上限理论几乎一样不存在某一个天生更快差异在编程范式、代码组织、灵活性、适用场景。底层共同点都运行在 Reactor Netty 非阻塞容器都返回MonoT / FluxT都不能写阻塞代码JDBC、RestTemplate、sleep都支持 SSE /ndjson/ 普通 json1. 代码直观对比① RestController注解式命令式 / 注解驱动RestController RequestMapping(/api/user) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } GetMapping public FluxUser list() { return userService.findAll(); } GetMapping(/{id}) public MonoUser 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 MonoServerResponse list(ServerRequest request) { FluxUser flux userService.findAll(); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(flux, User.class); } public MonoServerResponse 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 RouterFunctionServerResponse userRoutes(UserHandler userHandler) { return RouterFunctions.route() .GET(/api/user, userHandler::list) .GET(/api/user/{id}, userHandler::getById) .build(); } }关键点HandlerFunction 签名MonoServerResponse handle(ServerRequest request)ServerRequest封装请求path、param、header、bodyServerResponse构建响应status、header、body2. 核心区别对比表对比项RestController 注解式Functional Routing 函数式路由编程范式注解驱动类 方法注解类似 SpringMVC函数式RouterFunction HandlerFunctionlambda / 方法引用请求入参注解绑定PathVariableRequestParamRequestBody手动从ServerRequest提取无注解绑定响应输出直接返回MonoT / FluxT框架自动包装手动构建ServerResponse.ok()…body()自由度高路由注册注解扫描自动发现手动构造 RouterFunction Bean显式定义路由表AOP / 拦截标准 Spring AOP、ExceptionHandler、ControllerAdvice优先用filter()/before()/after()ControllerAdvice对函数路由不生效全局异常处理ControllerAdvice ExceptionHandler需要自定义WebExceptionHandler请求 body 解析RequestBody MonoUser自动解析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否则非阻塞优势全部丧失。补充小示例函数路由全局 filterBean public RouterFunctionServerResponse 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(); }