《Spring Boot 高级》5.2 WebFlux 请求处理

拆开 WebFlux 的请求处理链:DispatcherHandler 如何取代 DispatcherServlet、响应式 HandlerMapping 与 HandlerAdapter 各自返回什么、返回 Mono 的控制器方法如何被 HandlerResultHandler 适配成响应,以及 WebFilter 与 Servlet Filter、函数式端点与注解式端点的取舍。

本节目标:讲清 WebFlux 与 MVC 是两条独立栈(不是替换关系)、DispatcherHandler 取代 DispatcherServlet 后的处理链、返回 Mono 的控制器方法如何被适配成响应,以及 WebFilter 与函数式端点的定位。
适用版本:Spring Boot 4.1.x(Java 21)

5.2 WebFlux 请求处理

4.1 拆的是 MVC 那条链:DispatcherServlet → HandlerMapping → HandlerAdapter → HandlerResult。WebFlux 长得像,但每一个环节的类型都换成了响应式版本,而且它不是 MVC 的升级版——两条栈并存,各自解决不同问题。本节用本机 spring-webflux-7.0.9.jar 与 spring-web-7.0.9.jar 把这条链的每一环核实一遍。

延续借阅场景:本节用 GET /borrowings/{memberId} 返回该读者借阅记录流,GET /borrowings/{memberId}/events 用 SSE 推送借阅事件。

5.2.1 两条独立的栈,不是替换关系

先纠正一个常见误解:WebFlux 不是「MVC 的下一代」。它们是两套平行实现:

维度Spring MVCSpring WebFlux
起步依赖(4.x 新名)spring-boot-starter-webmvcspring-boot-starter-webflux
运行模型阻塞式,一请求一线程非阻塞,事件循环 + 少量线程
核心入口DispatcherServletDispatcherHandler
底层契约HttpServletRequest / HttpServletResponseServerWebExchange
默认服务器Tomcat(Servlet 容器)Reactor Netty
阻塞代码天然支持必须隔离,否则拖垮事件循环

两者不能同时启用。Boot 通过 org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer(本机 spring-boot-webflux-4.1.1.jar 核实)判断应用类型:类路径上只有 DispatcherHandler 就推断为 reactive,只有 DispatcherServlet 就推断为 servlet;两个都在(比如误引了两个 starter)会启动失败并提示需要显式指定 spring.main.web-application-type。

选型不看「谁更先进」,而看「瓶颈在哪」:大量外部 I/O、流式响应、需要长连接时 WebFlux 才有意义;以数据库同步访问为主的服务,用 MVC + 虚拟线程往往比硬改 WebFlux 更划算(虚拟线程见 7.2)。

5.2.2 DispatcherHandler:取代 DispatcherServlet 的入口

WebFlux 的入口是 org.springframework.web.reactive.DispatcherHandler(本机 spring-webflux-7.0.9.jar 核实):

public class DispatcherHandler
        implements org.springframework.web.server.WebHandler,
                   org.springframework.web.cors.reactive.PreFlightRequestHandler,
                   org.springframework.context.ApplicationContextAware {
  public reactor.core.publisher.Mono<Void> handle(org.springframework.web.server.ServerWebExchange);
  protected void initStrategies(org.springframework.context.ApplicationContext);
  public java.util.List<org.springframework.web.reactive.HandlerMapping> getHandlerMappings();
}

三点与 MVC 直接对应:

  1. 入口方法是 handle(ServerWebExchange): Mono<Void>,而不是 service(request, response): void。它返回的 Mono<Void> 代表「整个请求处理完成」这一个信号——请求的完成本身也是一个响应式信号。
  2. 策略在 initStrategies(ApplicationContext) 里从容器里捞(而不是 MVC 的 initStrategies 从 DispatcherServlet.properties 兜底),所以 WebFlux 的策略 bean 是容器管理的普通 bean,可以直接注入替换。
  3. handle 内部是声明式的链:找 handler → 找 adapter → 执行 → 处理结果 → 渲染异常,每一环都是 flatMap 串起来的 Mono,而不是嵌套调用。

DispatcherHandler 自己也实现了 WebHandler,这正是它接入过滤器链的方式(见 5.2.5)。

5.2.3 HandlerMapping 与 HandlerAdapter 的响应式版本

两个接口的签名都换了类型:

public interface org.springframework.web.reactive.HandlerMapping {
  reactor.core.publisher.Mono<Object> getHandler(ServerWebExchange exchange);
}

public interface org.springframework.web.reactive.HandlerAdapter {
  boolean supports(Object handler);
  reactor.core.publisher.Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler);
}

差别不只是「返回值包了 Mono」:

  • HandlerMapping.getHandler 返回 Mono<Object>:因为找 handler 的过程可能本身是异步的(比如要做响应式的内容协商)。空 Mono 表示没找到,DispatcherHandler 据此返回 404。
  • HandlerAdapter.handle 返回 Mono<HandlerResult>:注意是 HandlerResult(本机核实为 org.springframework.web.reactive.HandlerResult),里面装着 handler、returnValue、returnType、BindingContext——方法调用已经完成,但返回值还没被渲染。这与 MVC 里「HandlerAdapter 返回 ModelAndView」是同一个思路。

本机核实的三类 adapter 与它们的适用对象:

HandlerAdapter支持的 handler用途
RequestMappingHandlerAdapterHandlerMethod注解式 @RequestMapping 方法
HandlerFunctionAdapterHandlerFunction函数式端点(RouterFunction 路由到的)
SimpleHandlerAdapter实现 WebHandler 的对象兜底:supports 判断对象是否 WebHandler,是则直接 handle

对应地,WebFlux 也有两类 HandlerMapping:RequestMappingHandlerMapping(注解式,位于 org.springframework.web.reactive.result.method.annotation)与 RouterFunctionMapping(函数式,位于 org.springframework.web.reactive.function.server.support)。它们都由 WebFluxConfigurationSupport 注册成 bean——本机 javap 能看到 webHandler()、requestMappingHandlerMapping(...)、routerFunctionMapping(...)、resourceHandlerMapping(...) 等工厂方法。

5.2.4 返回 Mono 的控制器方法如何被适配

控制器写起来和 MVC 很像:

@RestController
public class BorrowingController {

    private final BorrowingService borrowingService;

    public BorrowingController(BorrowingService borrowingService) {
        this.borrowingService = borrowingService;
    }

    @GetMapping("/borrowings/{memberId}")
    public Flux<Borrowing> list(@PathVariable String memberId) {
        return borrowingService.findByMember(memberId);
    }

    @GetMapping(value = "/borrowings/{memberId}/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<BorrowingEvent> events(@PathVariable String memberId) {
        return borrowingService.events(memberId);
    }
}

但内部适配路径完全不同。RequestMappingHandlerAdapter 调用方法拿到返回值后,不会像 MVC 那样交给 HandlerMethodReturnValueHandler 去 writeWithMessageConverters,而是:

  1. 用 ReactiveAdapterRegistry 判断返回值是不是「响应式类型」(Mono / Flux / Kotlin 协程 / RxJava / CompletableFuture);
  2. 把它适配成一个统一的 Publisher,保持惰性,不在这里 block;
  3. 产出 HandlerResult,交给 HandlerResultHandler 渲染。

本机核实的 HandlerResultHandler 实现,各自负责一类返回值:

结果处理器负责的返回值
ResponseBodyResultHandler@ResponseBody / @RestController 的响应体(含 Flux → SSE / 流式 JSON)
ResponseEntityResultHandler返回 ResponseEntity 的方法
ViewResolutionResultHandler返回视图名 / ModelAndView(含 SSE 的 SseEmitter 等价物)
ServerResponseResultHandler函数式端点的 ServerResponse
WebFluxResponseStatusExceptionHandlerWebExceptionHandler 分支,把异常映射成响应状态

关键设计意图:整个链上没有一次阻塞。MVC 里 HandlerAdapter 必须把响应体当场写进 HttpServletResponse;WebFlux 里 HandlerResultHandler 返回的仍是一个 Mono<Void>,真正的写由 ServerHttpResponse 在订阅时驱动。这就是「返回 Mono 的接口能支撑高并发」的原因——线程在等待 I/O 期间被释放回事件循环。

5.2.5 WebFilter 与 Servlet Filter 的差异

WebFilter 定义在 spring-web 的 org.springframework.web.server 包(本机核实,不在 spring-webflux):

public interface org.springframework.web.server.WebFilter {
  reactor.core.publisher.Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

public interface org.springframework.web.server.WebFilterChain {
  reactor.core.publisher.Mono<Void> filter(ServerWebExchange exchange);
}

与 Servlet Filter 的差别是一组连锁反应:

维度Servlet FilterWebFilter
签名doFilter(req, resp, chain) 返回 voidfilter(exchange, chain) 返回 Mono<Void>
参数两个对象(请求 / 响应)一个 ServerWebExchange(请求 + 响应 + 属性)
「放行」chain.doFilter(req, resp)chain.filter(exchange)
「响应之后」写在 chain.doFilter 调用之后的代码用 then(Mono) 挂到返回的 Mono 上
是否阻塞是,一请求占一线程否,返回的信号驱动

第四行是最容易写错的地方。Servlet 里「记录耗时」写在 chain.doFilter 之后即可;WebFlux 里 chain.filter(exchange) 是立即返回的(它只是返回一个 Mono),后面的代码会在请求处理之前就执行。正确写法是把它接到返回信号上:

@Component
public class TimingWebFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        long start = System.nanoTime();
        return chain.filter(exchange)
                .doFinally(signal -> {
                    long cost = (System.nanoTime() - start) / 1_000_000;
                    System.out.println(exchange.getRequest().getPath() + " -> " + cost + " ms");
                });
    }
}

过滤器链的组装在 org.springframework.web.server.adapter.WebHttpHandlerBuilder(本机核实)里:它把 WebFilter 列表、WebExceptionHandler 列表与 DispatcherHandler 包成一个 HttpHandler。链的具体实现是 org.springframework.web.server.handler.DefaultWebFilterChain,构造签名 (WebHandler, List<WebFilter>)——链尾固定是那个 WebHandler(也就是 DispatcherHandler),每个 filter 调 chain.filter 就前进一环。

异常处理走另一条接口:WebExceptionHandler.handle(ServerWebExchange, Throwable): Mono<Void>(本机核实)。Boot 的 WebFlux 自动配置提供了 org.springframework.boot.webflux.autoconfigure.error.AbstractErrorWebExceptionHandler 作为默认实现(本机 spring-boot-webflux-4.1.1.jar 核实),它负责把未捕获异常渲染成 /error 响应。

5.2.6 函数式端点:RouterFunction 与 HandlerFunction

注解式之外,WebFlux 提供了一套函数式 API。核心是两个函数式接口(本机核实):

public interface org.springframework.web.reactive.function.server.HandlerFunction<T extends ServerResponse> {
  Mono<T> handle(ServerRequest request);
}

public interface org.springframework.web.reactive.function.server.RouterFunction<T extends ServerResponse> {
  Mono<HandlerFunction<T>> route(ServerRequest request);
}

注意 RouterFunction.route 返回的是 Mono<HandlerFunction<T>>——「找到的处理器」本身也是异步找到的,找不到返回空 Mono(对应 404)。写法:

@Configuration
public class BorrowingRouter {

    @Bean
    public RouterFunction<ServerResponse> borrowingRoutes(BorrowingHandler handler) {
        return RouterFunctions.route()
                .GET("/fn/borrowings/{memberId}", handler::list)
                .nest(path("/fn/borrowings/{memberId}"), builder -> builder
                        .GET("/events", handler::events))
                .build();
    }
}
@Component
public class BorrowingHandler {

    private final BorrowingService borrowingService;

    public BorrowingHandler(BorrowingService borrowingService) {
        this.borrowingService = borrowingService;
    }

    public Mono<ServerResponse> list(ServerRequest request) {
        String memberId = request.pathVariable("memberId");
        return ServerResponse.ok().body(borrowingService.findByMember(memberId), Borrowing.class);
    }

    public Mono<ServerResponse> events(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.TEXT_EVENT_STREAM)
                .body(borrowingService.events(request.pathVariable("memberId")), BorrowingEvent.class);
    }
}

本机核实的静态入口:RouterFunctions.route()(返回 Builder)、RouterFunctions.route(RequestPredicate, HandlerFunction)、RouterFunctions.nest(...)、RouterFunctions.resources(...)、RouterFunctions.toWebHandler(...)。ServerResponse 提供 ok()、status(int)、created(URI)、badRequest()、unprocessableContent() 等(本机核实)。

两种风格的取舍:

维度注解式函数式
可读性声明式,路由与实现同处一地路由集中在一处,处理逻辑分开
组合能力靠 @RequestMapping 组合注解原生可组合:and / andNest / filter
复用与测试需要起上下文RouterFunction 是普通对象,可直接单测路由
生态适配注解、参数解析器、验证全都现成需手写 ServerRequest 解析
适合场景常规 CRUD、团队熟悉 MVC网关式转发、动态路由、需要程序化拼装路由

选择建议:默认用注解式,只有在「路由需要按条件程序化拼装」「同一 handler 要挂到多组路径」「想脱离 Spring 上下文测路由」时才上函数式。两者可以并存——RequestMappingHandlerMapping 与 RouterFunctionMapping 是两个并列的 HandlerMapping,DispatcherHandler 按顺序问过去,谁先匹配谁处理。

5.2.7 自动配置做了什么

Boot 4.x 把 WebFlux 相关自动配置收进了独立模块 spring-boot-webflux(本机核实存在,对应 spring-boot-starter-webflux;starter 名在 4.0 未被改名)。关键类:

类作用
org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration启用 WebFlux,注册静态资源、欢迎页、表单/会话等
org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration把 WebFilter / WebExceptionHandler / DispatcherHandler 组装成 HttpHandler
org.springframework.boot.webflux.autoconfigure.error.AbstractErrorWebExceptionHandler默认错误响应渲染
org.springframework.boot.webflux.WebFluxWebApplicationTypeDeducer推断应用类型

常用配置项(本机 spring-boot-webflux-4.1.1.jar 的元数据核实):spring.webflux.base-path(统一前缀)、spring.webflux.static-path-pattern、spring.webflux.default-html-escape、spring.webflux.problemdetails.enabled,以及 4.0 新增的 spring.webflux.apiversion.*(API 版本化,与 MVC 的 spring.mvc.apiversion.* 对应)。

要替换默认行为,注入 WebFluxConfigurer(本机核实接口)即可——configureHttpMessageCodecs、addFormatters、addCorsMappings、configurePathMatching、configureArgumentResolvers 等都是默认方法,按需覆写。

5.2.8 本机可以做的验证

export JAVA_HOME=/tmp/springboot_book/jdk-21.0.12.1+1/Contents/Home
J=/tmp/springboot_book/jars
SW=~/.m2/repository/org/springframework/spring-web/7.0.9/spring-web-7.0.9.jar

# DispatcherHandler 的接口与入口方法
"$JAVA_HOME/bin/javap" -cp "$J/spring-webflux-7.0.9.jar" org.springframework.web.reactive.DispatcherHandler

# 响应式 HandlerMapping / HandlerAdapter 的签名
"$JAVA_HOME/bin/javap" -cp "$J/spring-webflux-7.0.9.jar" org.springframework.web.reactive.HandlerMapping
"$JAVA_HOME/bin/javap" -cp "$J/spring-webflux-7.0.9.jar" org.springframework.web.reactive.HandlerAdapter

# WebFilter 其实在 spring-web 里
"$JAVA_HOME/bin/javap" -cp "$SW" org.springframework.web.server.WebFilter
"$JAVA_HOME/bin/javap" -cp "$SW" org.springframework.web.server.handler.DefaultWebFilterChain

# 函数式端点的两个接口
"$JAVA_HOME/bin/javap" -cp "$J/spring-webflux-7.0.9.jar" \
  org.springframework.web.reactive.function.server.RouterFunction

要观察整条链,最快的办法是在 DispatcherHandler.handle 与 DefaultWebFilterChain.filter 下断点,请求一次 /borrowings/m-1:断点会依次命中 filter → dispatcher → mapping → adapter → result handler。

5.2.9 知道之后能做什么

排「请求挂住」。 若某个接口一直不返回,先确认 HandlerAdapter 返回的 Mono 是否被订阅、以及链上是否混入了 block()(下一节展开)。

排「过滤器顺序不对」。 WebFilter 的顺序由 @Order / Ordered 决定,不是声明顺序。需要「认证先于日志」时,显式给 @Order。

排「404 但路由看着没错」。 记住 HandlerMapping.getHandler 返回空 Mono 就是 404 的来源;注解式与函数式是两个并列 mapping,函数式路由若被前面的 mapping 抢走,要检查路径是否重叠。

小结

  • WebFlux 与 MVC 是两条独立栈,起步依赖分别是 spring-boot-starter-webflux 与 spring-boot-starter-webmvc,不能同时启用。
  • DispatcherHandler.handle 返回 Mono<Void>;HandlerMapping.getHandler 返回 Mono<Object>,HandlerAdapter.handle 返回 Mono<HandlerResult>,整条链无阻塞。
  • 返回 Mono / Flux 的控制器方法由 ReactiveAdapterRegistry 适配后交给 HandlerResultHandler 渲染,真正的写发生在订阅时。
  • WebFilter 在 spring-web 的 org.springframework.web.server 包,返回 Mono<Void>;「响应之后」的逻辑要接在返回信号上,不能写在 chain.filter 之后。
  • 函数式端点(RouterFunction / HandlerFunction)与注解式并列存在,适合程序化拼装路由,不是替代关系。

背压有了、请求链也有了,剩下最后一环:如果链上某处必须调用阻塞 API,怎么放才不拖垮事件循环。

阅读导航:上一节:5.1 响应式类型与背压 · 下一节:5.3 阻塞代码的隔离 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计