Spring Framework 7.0.9 深度剖析-19-WebFlux 响应式架构

14 阅读56分钟

Spring Framework 7.0.9 深度剖析-19-WebFlux 响应式架构

19.1 本章导读

Spring Framework 7.0.9 同时提供两套服务端 Web 编程模型:基于 Servlet 3.1+ 异步 IO 的 spring-webmvc,以及基于 Reactive Streams 的 spring-webflux。本章只剖析后者——WebFlux 的响应式架构。与 Servlet 模型"一个请求绑定一个线程、阻塞到底"的哲学根本不同,WebFlux 的核心命题是:以少量非阻塞线程承载海量并发连接,整条处理链全部返回 Mono/Flux,错误与完成都通过信号沿订阅链传播,而不是通过异常向上抛出。

本章要解决的问题按层次排列:

  1. 最底层的抽象是什么。WebFlux 如何把 Reactor Netty(或 Jetty、Tomcat、Undertow)的事件循环与自身的编程模型解耦——答案是 HttpHandler 接口与一组 ServerHttpRequest/ServerHttpResponse 适配器。
  2. 请求如何被一层层装饰。WebHttpHandlerBuilder 如何把 WebFilter 列表、WebExceptionHandler 列表、目标 WebHandler 编织成一条处理链,并最终适配成 HttpHandler。
  3. 请求如何被分发。DispatcherHandler 如何遍历 HandlerMapping 找到 handler、用 HandlerAdapter 调用、再用 HandlerResultHandler 把返回值写回响应。
  4. 注解与函数式两种编程模型分别如何落地。@RequestMapping 控制器如何完成参数解析(27 个内置 HandlerMethodArgumentResolver)与返回值写回;路由函数(RouterFunctions.route(...))又如何完成匹配与调用。
  5. 响应式客户端 WebClient 的链路结构。
  6. 一次 GET 请求从 Netty 事件循环到响应写回的完整时序,以及响应式模型带来的线程、背压、上下文(Context)问题与常见坑。

涉及的核心模块是 spring-webflux(org.springframework.web.reactive 包族)与 spring-web(org.springframework.http.server.reactive、org.springframework.web.server 包族)。需要特别说明的一个版本事实是:HttpHandler 抽象、ServerHttpRequest/Response、WebHandler、WebFilter、WebExceptionHandler、装配链(WebHttpHandlerBuilder/HttpWebHandlerAdapter/ExceptionHandlingWebHandler/FilteringWebHandler)全部位于 spring-web 模块,不在 spring-webflux;spring-webflux 只负责策略层(DispatcherHandler、注解/函数式编程模型、WebClient)。这种分层意味着 spring-web 的响应式契约甚至可以独立于任何编程模型存在——事实上 RouterFunctions.toHttpHandler(...) 就是不经过 DispatcherHandler 直接组装 HttpHandler 的另一条路径。

前置知识:了解 Reactor 的 Mono(0..1 异步序列)与 Flux(0..N)基本语义;知道响应式流的订阅(subscribe)触发执行、信号(onNext/onError/onComplete)沿链传播这一事实即可,不要求读过 Reactor 源码(Reactor 不在本仓库内)。读完本章,读者应当能在不看任何外部文档的情况下,把一个真实 GET 请求在 7.0.9 源码中的每一跳讲清楚,并知道 block()、ThreadLocal、共享可变状态这些雷区具体是被哪段源码机制"放大"成故障的。

19.2 核心概念:响应式基础与分层抽象

19.2.1 Reactor 使用层面的最小必要概念

WebFlux 全链路的返回类型是 reactor.core.publisher.Mono 与 reactor.core.publisher.Flux。站在 spring-webflux 使用层面,只需建立四个认识:

  1. Mono<T> 是 0..1 个元素的异步序列,Flux<T> 是 0..N 个元素的异步序列。WebFlux 里"处理完一个请求"对应 Mono<Void>——它不携带数据,只表达"完成"这一时间点;响应体的流式写出对应 Flux<DataBuffer>。
  2. 订阅才触发执行(lazy)。HttpHandler.handle(...) 返回的是一个未执行的 Mono<Void>,真正的执行发生在底层服务器(Reactor Netty)订阅它之时。这解释了为什么 WebFlux 里大量使用 Mono.defer(...):把"创建序列"的动作推迟到订阅时刻,否则装配期就会执行请求相关代码,此时 ServerWebExchange 尚不存在。
  3. 错误是信号,不是异常。链路上任何一环失败,产生的是沿订阅链向下游传播的 onError 信号;下游用 onErrorResume(兜底并返回新序列)、doOnError(仅记录)、onErrorMap(转换)这类操作符处理它。WebFlux 的异常处理体系——ExceptionHandlingWebHandler、DispatcherHandler.handleResultMono——本质上全是围绕 onErrorResume 编织的。
  4. 背压(Backpressure)。响应式流规范要求消费者(Subscriber)通过 Subscription.request(n) 向上游声明自己能消费多少元素,上游据此限速。WebFlux 的请求体读取(ServerHttpRequest.getBody() 返回 Flux<DataBuffer>)与响应体写出(ServerHttpResponse.writeWith(Publisher))都是背压感知的:Netty channel 可写水位下降时自然向下游拉低请求速率;应用层若用 collectList() 把无限流聚合成列表,背压失效时会直接 OOM。这是后文"常见问题"一节的源码级伏笔。

19.2.2 五层抽象总览

把 7.0.9 的源码按包路径自底向上排开,WebFlux 是一条严格的分层链:

层次接口/类所在包(模块)一句话职责
L0 运行时适配ReactorHttpHandlerAdapterorg.springframework.http.server.reactive(spring-web)把 Reactor Netty 的 HttpServerRequest/Response 桥接为 ServerHttpRequest/Response
L1 HTTP 契约HttpHandlerorg.springframework.http.server.reactive(spring-web)全框架最顶层的低层契约:Mono<Void> handle(ServerHttpRequest, ServerHttpResponse)
L2 交换与过滤WebHandler / WebFilter / WebExceptionHandler / ServerWebExchangeorg.springframework.web.server(spring-web)以 ServerWebExchange 为中心的处理链抽象
L3 装配WebHttpHandlerBuilder / HttpWebHandlerAdapter / FilteringWebHandler / ExceptionHandlingWebHandlerorg.springframework.web.server.adapter、...handler(spring-web)把 L2 组件编织成链并适配回 L1
L4 分发与编程模型DispatcherHandler 及 HandlerMapping/Adapter/ResultHandler 策略族org.springframework.web.reactive(spring-webflux)注解控制器、函数式路由的分发核心

调用方向永远向下:Netty 事件循环 → L0 适配器 → L1 HttpHandler → L3 HttpWebHandlerAdapter(创建 ServerWebExchange)→ L3 ExceptionHandlingWebHandler → L3 FilteringWebHandler(L2 WebFilter 链)→ L4 DispatcherHandler → 具体 HandlerResultHandler → 响应体通过 ServerHttpResponse.writeWith(...) 写回 L0。下一节开始逐层贴源码剖析。

19.3 源码剖析(一):HttpHandler 抽象层——与运行时的唯一边界

19.3.1 HttpHandler:整个响应式 Web 体系的最底层契约

先看全框架最短、也最重要的一个接口(spring-web/src/main/java/org/springframework/http/server/reactive/HttpHandler.java 第 43-53 行):

public interface HttpHandler {

	/**
	 * Handle the given request and write to the response.
	 * @param request current request
	 * @param response current response
	 * @return indicates completion of request handling
	 */
	Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);

}

逐句解读:

  1. 输入不是"请求体",而是两个抽象对象:ServerHttpRequest 与 ServerHttpResponse。这两个接口(稍后展开)屏蔽了一切运行时差异——Reactor Netty、Jetty、Tomcat、Undertow、甚至 Servlet 3.1+ 容器,都各自有一对实现类(本目录下的 ReactorServerHttpRequest、JettyCoreServerHttpRequest、ServletServerHttpRequest 等)。
  2. 返回 Mono<Void> 而不是 void。这是整个响应式架构的命门:方法调用本身只是"声明要怎么处理",真正的处理发生在订阅时;Mono 终结(成功完成 / onError)才代表请求处理完成。Javadoc 注释 "indicates completion of request handling" 说的正是这一点。
  3. 类注释明确了它的定位:它是 "common denominator across different runtimes"(跨运行时的最大公约数),而 WebFilter、WebSession、ServerWebExchange 这些更高级的构件在 org.springframework.web.server 包;注解控制器与函数式处理器在 spring-webflux 模块。也就是说,L1 契约刻意保持"裸"——不认识 exchange、不认识过滤器,只认识读写两个方向的消息。

这个接口就是 WebFlux 与 Servlet 模型的分水岭:在 Servlet 世界里,控制器方法签名直接绑定 HttpServletRequest/HttpServletResponse 这两个容器类型;而 WebFlux 里,任何业务代码永远不会触达容器类型,所有容器类型只在 L0 适配器里出现一次。

19.3.2 ServerHttpRequest / ServerHttpResponse:读写两个方向的消息抽象

ServerHttpRequest(spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java 第 41 行起)的声明是:

public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {

两个父接口把能力拼齐:

  • org.springframework.http.HttpRequest(spring-web/src/main/java/org/springframework/http/HttpRequest.java)提供 HttpMethod getMethod()、URI getURI()、Map<String, Object> getAttributes()——即"方法 + URI"这两个最基础的请求元数据;
  • org.springframework.http.ReactiveHttpInputMessage(spring-web/src/main/java/org/springframework/http/ReactiveHttpInputMessage.java)继承自 HttpMessage(提供 HttpHeaders getHeaders()),并声明了响应式输入的核心:
Flux<DataBuffer> getBody();

请求体是一个 Flux<DataBuffer>——即"随来随推的字节流",而不是一次性读进内存的 byte[]。这是背压在请求侧的体现:上游(容器)按解析出的网络块推 DataBuffer,下游(如 @RequestBody 的 Jackson 解码器)通过 request(n) 控制节奏。

ServerHttpRequest 自身声明的其余方法(同文件第 49-105 行):

String getId();                                    // 请求/连接 id,日志关联用
RequestPath getPath();                             // 结构化路径:contextPath + pathWithinApplication
MultiValueMap<String, String> getQueryParams();    // 已解码查询参数
MultiValueMap<String, HttpCookie> getCookies();     // Cookie
default InetSocketAddress getLocalAddress() { ... }  // 7.0 版 Builder 才补上 localAddress
default InetSocketAddress getRemoteAddress() { ... }
default SslInfo getSslInfo() { ... }
default ServerHttpRequest.Builder mutate() {       // 包装成 Decorator 做变更
	return new DefaultServerHttpRequestBuilder(this);
}

注意 getPath() 返回的不是 String 而是 RequestPath——它把路径切成 contextPath()(应用挂载路径)与 pathWithinApplication()(应用内匹配路径)两段,后者才交给 PathPattern 做路由匹配。这个设计让一个 JVM 里挂多个应用(ContextPathCompositeHandler)成为可能。

响应侧 ServerHttpResponse(spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java 第 34 行起):

public interface ServerHttpResponse extends ReactiveHttpOutputMessage {

	boolean setStatusCode(@Nullable HttpStatusCode status);
	@Nullable HttpStatusCode getStatusCode();
	default boolean setRawStatusCode(@Nullable Integer value) { ... }
	MultiValueMap<String, ResponseCookie> getCookies();
	void addCookie(ResponseCookie cookie);
}

父接口 ReactiveHttpOutputMessage(spring-web/src/main/java/org/springframework/http/ReactiveHttpOutputMessage.java)则定义了写出侧的四个关键操作:

DataBufferFactory bufferFactory();
void beforeCommit(Supplier<? extends Mono<Void>> action);   // 提交前回调(写 header/Etag 类动作)
boolean isCommitted();
Mono<Void> writeWith(Publisher<? extends DataBuffer> body);  // 一次性写出整个 Publisher
Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body); // 分块冲刷
Mono<Void> setComplete();

writeWith(Publisher) 是写回路径的终点:任何 HandlerResultHandler 的最终动作,都是把业务对象序列化成 Publisher<DataBuffer> 后调用它。setStatusCode 的返回值是 boolean——返回 false 表示响应已提交(committed),状态码改不动了。这个细节是后文"响应已提交后异常如何处理"的源码依据:一旦 body 开始写出,状态码就冻结了。

19.3.3 ReactorHttpHandlerAdapter:Netty 事件循环与 Spring 之间的桥

现在看 L0 适配器(spring-web/src/main/java/org/springframework/http/server/reactive/ReactorHttpHandlerAdapter.java 第 40-78 行,7.0.9 完整源码):

public class ReactorHttpHandlerAdapter implements BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>> {

	private static final Log logger = HttpLogging.forLogName(ReactorHttpHandlerAdapter.class);

	private final HttpHandler httpHandler;


	public ReactorHttpHandlerAdapter(HttpHandler httpHandler) {
		Assert.notNull(httpHandler, "HttpHandler must not be null");
		this.httpHandler = httpHandler;
	}


	@Override
	public Mono<Void> apply(HttpServerRequest reactorRequest, HttpServerResponse reactorResponse) {
		NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc());
		try {
			ReactorServerHttpRequest request = new ReactorServerHttpRequest(reactorRequest, bufferFactory);
			ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory);

			if (request.getMethod() == HttpMethod.HEAD) {
				response = new HttpHeadResponseDecorator(response);
			}

			return this.httpHandler.handle(request, response)
					.doOnError(ex -> logger.trace(request.getLogPrefix() + "Failed to complete: " + ex.getMessage()))
					.doOnSuccess(aVoid -> logger.trace(request.getLogPrefix() + "Handling completed"));
		}
		catch (URISyntaxException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to get request URI: " + ex.getMessage());
			}
			reactorResponse.status(HttpResponseStatus.BAD_REQUEST);
			return Mono.empty();
		}
	}

}

逐段解读:

  1. 它为什么是 BiFunction<HttpServerRequest, HttpServerResponse, Mono<Void>>。Reactor Netty 的 HttpServer.handle(...) 要求传入一个"请求进来时调用的函数"——签名恰好是 BiFunction。Spring 实现这个函数接口,把 Netty 的两个原始对象包一层,再委托给内部持有的 HttpHandler。这就是整个应用与 Netty 的唯一接触点:应用代码从头到尾不认识 HttpServerRequest。
  2. NettyDataBufferFactory。DataBuffer 是 spring-core 对字节缓冲区的抽象(spring-core 的 io.buffer 包),其 Netty 实现直接包装 io.netty.buffer.ByteBufAllocator——写出的 DataBuffer 最终零拷贝地落到 Netty 的 ByteBuf 上。buffer factory 用 reactorResponse.alloc() 创建,保证与连接的分配器绑定。
  3. ReactorServerHttpRequest / ReactorServerHttpResponse 的构造本身可能抛 URISyntaxException。URI 解析失败(非法转义字符)时,Spring 直接在 Netty 响应上写 400 并返回 Mono.empty()——注意这里没有走后续任何处理链,因为 ServerWebExchange 根本建不起来。这是"框架最外层兜底"的第一处:非法 URI 请求在 adapter 层就被终结。
  4. HEAD 请求的特殊装饰。request.getMethod() == HttpMethod.HEAD 时,响应被包上 HttpHeadResponseDecorator——装饰器在写出 body 时丢弃全部 DataBuffer、只保留 header 与 Content-Length。这是 HTTP 语义要求的:HEAD 与 GET 同路径同 header,但绝不返回 body。
  5. 返回的 Mono 上挂了 doOnError/doOnSuccess 的 trace 日志。它们不改变信号,只记录"处理完成/失败"。注意日志前缀来自 request.getLogPrefix()——每个请求有唯一前缀(如 [id=..., L:/... - R:/...]),把单连接内多条日志串起来。

这个类的设计哲学可以一句话总结:把"容器类型"封锁在一个 78 行的类里,其余全部世界用 Spring 自己的抽象。同目录下还有 TomcatHttpHandlerAdapter、JettyCoreHttpHandlerAdapter、ServletHttpHandlerAdapter——同一个 HttpHandler 应用可以原样跑在四种容器上,这正是 19.3.1 引用里 "common denominator across different runtimes" 的兑现。

19.3 源码剖析(二):装配链——WebHttpHandlerBuilder 如何织出一条装饰链

19.3.4 目标 WebHandler:DispatcherHandler 就是那个 "webHandler" bean

在进入装配链之前,先明确被装配的"目标"是什么。WebHttpHandlerBuilder 类注释(spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java 第 50-68 行)说得很直白:它做两件事——把一个目标 WebHandler 先用一组 WebFilter 装饰、再用一组 WebExceptionHandler 装饰,最后把这整条链适配成 HttpHandler。

而在注解编程模型下,那个目标 WebHandler 就是 DispatcherHandler。证据在 spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java 第 125-128 行:

@Bean
public DispatcherHandler webHandler() {
	return new DispatcherHandler();
}

bean 名固定为 "webHandler",对应 WebHttpHandlerBuilder 第 72 行的常量 WEB_HANDLER_BEAN_NAME = "webHandler"。这就是"约定优于配置"的闭合点:@EnableWebFlux 导入的配置类注册了一个名为 webHandler 的 DispatcherHandler;WebHttpHandlerBuilder.applicationContext(context) 按这个名字把它捞出来。

三个被装饰的接口(均在 spring-web/src/main/java/org/springframework/web/server/):

// WebHandler.java
public interface WebHandler {
	Mono<Void> handle(ServerWebExchange exchange);
}

// WebFilter.java
public interface WebFilter {
	Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

// WebExceptionHandler.java
public interface WebExceptionHandler {
	Mono<Void> handle(ServerWebExchange exchange, Throwable ex);
}

三者的参数差异值得玩味:WebHandler 只收 exchange;WebFilter 额外收 WebFilterChain(决定是否放行到下一环);WebExceptionHandler 额外收一个已经发生的 Throwable——它的输入就是 onError 信号本身。

19.3.5 applicationContext():从容器里收集装配原料

WebHttpHandlerBuilder.applicationContext(ApplicationContext)(同文件第 174-231 行)是装配的起点。贴关键片段:

public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {

	WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
			context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);

	List<WebFilter> webFilters = context
			.getBeanProvider(WebFilter.class)
			.orderedStream()
			.toList();
	builder.filters(filters -> filters.addAll(webFilters));

	List<WebExceptionHandler> exceptionHandlers = context
			.getBeanProvider(WebExceptionHandler.class)
			.orderedStream()
			.toList();
	builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers));
	……

逐段解读:

  1. 目标 handler 按名取:context.getBean("webHandler", WebHandler.class)——容器里必须存在这个 bean,否则装配直接失败。
  2. 所有 WebFilter bean 被一次性收集:注意是 getBeanProvider(WebFilter.class).orderedStream()——按 @Order/Ordered 排序。这意味着用户写的每个 WebFilter 实现类只要注册成 bean,就自动进链,顺序由 order 决定,无需手工拼装。这是 WebFlux 与 Servlet 过滤器注册方式的一大区别(Servlet 靠 FilterRegistrationBean 显式声明 URL 模式与顺序)。
  3. WebExceptionHandler 同理自动收集。在默认 @EnableWebFlux 应用里,容器中恰好有一个框架提供的 WebExceptionHandler bean:WebFluxConfigurationSupport.responseStatusExceptionHandler()(@Order(0)),它返回 new WebFluxResponseStatusExceptionHandler()——这是默认注册的唯一一个全局异常处理器,职责只是把 ResponseStatusException(及带 @ResponseStatus 注解的异常)翻译成响应状态码。
  4. 随后按名尝试取 webSessionManager、serverCodecConfigurer、localeContextResolver、forwardedHeaderTransformer,取不到就静默回退默认值——每个 try/catch (NoSuchBeanDefinitionException) 都注释着 "Fall back on default"。

19.3.6 build():装饰顺序与适配顺序

build() 方法(同文件第 427-459 行)是装配链的全部精华:

public HttpHandler build() {
	WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
	decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);

	HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
	if (this.sessionManager != null) {
		adapted.setSessionManager(this.sessionManager);
	}
	if (this.codecConfigurer != null) {
		adapted.setCodecConfigurer(this.codecConfigurer);
	}
	……
	adapted.afterPropertiesSet();

	return (this.httpHandlerDecorator != null ? this.httpHandlerDecorator.apply(adapted) : adapted);
}

两行 new 决定了整条链的嵌套结构,必须按读代码的方向理解"外层包内层":

  • new FilteringWebHandler(webHandler, filters):过滤器链包在目标 handler 外面;
  • new ExceptionHandlingWebHandler(decorated, handlers):异常处理再包在过滤器链外面;
  • new HttpWebHandlerAdapter(decorated):适配器再包最外面——它同时实现 HttpHandler。

因此一个请求的实际调用顺序是:

HttpWebHandlerAdapter.handle(request, response)      ← L1 契约入口
  └─ 创建 ServerWebExchange
     └─ ExceptionHandlingWebHandler.handle(exchange) ← 最外层:兜住全链 onError
        └─ FilteringWebHandler.handle(exchange)      ← WebFilter 链
           └─ DefaultWebFilterChain → ... → DispatcherHandler.handle(exchange)

注意装饰方向与直觉相反:异常处理器包在过滤器外层,意味着它能捕获包括过滤器在内的全链路错误;而过滤器链只能捕获"放行之后"的错误。最后 httpHandlerDecorator(可挂 Micrometer observation 等)还能再包一层最外层。

19.3.7 HttpWebHandlerAdapter:创建 exchange,也是全链最后的兜底

HttpWebHandlerAdapter(spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java 第 68 行)声明 extends WebHandlerDecorator implements HttpHandler——它既是 WebHandler 又是 HttpHandler,是两个契约之间的适配器。

其 handle(ServerHttpRequest, ServerHttpResponse)(第 292-326 行)是整个 L1→L2 的转换点:

@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
	if (this.forwardedHeaderTransformer != null) {
		try {
			request = this.forwardedHeaderTransformer.apply(request);
		}
		catch (Throwable ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to apply forwarded headers to " + formatRequest(request), ex);
			}
			response.setStatusCode(HttpStatus.BAD_REQUEST);
			return response.setComplete();
		}
	}
	ServerWebExchange exchange = createExchange(request, response);

	LogFormatUtils.traceDebug(logger, traceOn ->
			exchange.getLogPrefix() + formatRequest(exchange.getRequest()) +
					(traceOn ? ", headers=" + formatHeaders(exchange.getRequest().getHeaders()) : ""));

	ServerRequestObservationContext observationContext = new ServerRequestObservationContext(
			exchange.getRequest(), exchange.getResponse(), exchange.getAttributes());
	exchange.getAttributes().put(
			ServerRequestObservationContext.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);

	if (this.defaultHtmlEscape != null) {
		exchange.getAttributes().put(ServerWebExchange.HTML_ESCAPE_ATTRIBUTE, this.defaultHtmlEscape);
	}

	return getDelegate().handle(exchange)
			.doOnSuccess(aVoid -> logResponse(exchange))
			.onErrorResume(ex -> handleUnresolvedError(exchange, observationContext, ex))
			.tap(() -> new ObservationSignalListener(observationContext))
			.then(exchange.cleanupMultipart())
			.then(Mono.defer(response::setComplete));
}

逐段解读:

  1. ForwardedHeader 处理在 exchange 创建之前。若配置了 ForwardedHeaderTransformer,先用它改写 request(处理 X-Forwarded-Host 等代理头);改写抛异常直接 400 返回——与 adapter 层的 URI 解析失败同款的"最外层兜底"。
  2. createExchange(request, response)(第 328-331 行):
protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
	return new DefaultServerWebExchange(request, response, this.sessionManager,
			getCodecConfigurer(), getLocaleContextResolver(), this.applicationContext);
}

ServerWebExchange 是一次请求的"上下文总管":持有 request、response、属性 map、WebSessionManager、ServerCodecConfigurer(消息读写器)、LocaleContextResolver、以及共享的 ApplicationContext。后续所有组件(参数解析器、消息写器)都从 exchange 拿依赖,而不是各自持有。 3. observation context 挂进 exchange 属性。这是 Micrometer 可观测性的入口:一次 HTTP 请求对应一个 ServerRequestObservationContext,在链终止时记录耗时与状态。 4. 委托链返回的 Mono 上串了四段后置处理:

  • doOnSuccess(aVoid -> logResponse(exchange)):成功时打完成日志(含状态码);
  • onErrorResume(ex -> handleUnresolvedError(...)):整条链所有 WebExceptionHandler 都没接住的错误,最后落在这里——先尝试把状态码置为 500 并记 error 日志;若响应已提交(setStatusCode 返回 false)且错误不是"客户端断开",则把错误继续向上抛给服务器(Mono.error(ex)),由 Netty 记录;
  • .tap(...):用 Reactor 的 signal listener 同步 observation 的 start/stop;
  • .then(exchange.cleanupMultipart()).then(Mono.defer(response::setComplete)):无论成功失败,最后清理 multipart 临时资源、并确保 setComplete() 被调用——把 header 真正刷到下层。

handleUnresolvedError(第 358-382 行)的三分支值得单独看:

if (response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR)) {
	logger.error(logPrefix + "500 Server Error for " + formatRequest(request), ex);
	return Mono.empty();
}
else if (disconnectedClientHelper.checkAndLogClientDisconnectedException(ex)) {
	observationContext.setConnectionAborted(true);
	return Mono.empty();
}
else {
	logger.error(logPrefix + "Error [" + ex + "] for " + formatRequest(request) +
			", but ServerHttpResponse already committed (" + response.getStatusCode() + ")");
	return Mono.error(ex);
}
  • 分支一:响应还没提交 → 写 500,请求以"已处理完"终结;
  • 分支二:响应已提交,但错误本身是"客户端断开连接"(pipe closed / connection reset)→ 不打 error 日志,静默收尾(这类异常在高并发下很常见,打满日志没意义);
  • 分支三:响应已提交、且是真异常 → 无法再改状态码,只能把错误抛回 Netty——这是 WebFlux 错误处理里"晚了"的典型:body 写了一半出错,HTTP 层面没有任何补救手段。

19.3.8 ExceptionHandlingWebHandler:把一组 WebExceptionHandler 编成 onErrorResume 链

ExceptionHandlingWebHandler(spring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java)是"错误处理装饰器"。它的构造器与 handle 方法(第 56-88 行):

public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
	super(delegate);
	List<WebExceptionHandler> handlersToUse = new ArrayList<>();
	handlersToUse.add(new CheckpointInsertingHandler());
	handlersToUse.addAll(handlers);
	this.exceptionHandlers = Collections.unmodifiableList(handlersToUse);
}

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	Mono<Void> completion;
	try {
		completion = super.handle(exchange);
	}
	catch (Throwable ex) {
		completion = Mono.error(ex);
	}

	for (WebExceptionHandler handler : this.exceptionHandlers) {
		completion = completion.doOnError(error -> exchange.getAttributes().put(HANDLED_WEB_EXCEPTION, error))
				.onErrorResume(ex -> handler.handle(exchange, ex));
	}
	return completion;
}

逐段解读:

  1. 构造器强制在用户处理器列表最前面插入一个 CheckpointInsertingHandler(第 97-108 行):
private static class CheckpointInsertingHandler implements WebExceptionHandler {

	@Override
	public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
		ServerHttpRequest request = exchange.getRequest();
		String rawQuery = request.getURI().getRawQuery();
		String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
		HttpMethod httpMethod = request.getMethod();
		String description = "HTTP " + httpMethod + " \"" + request.getPath() + query + "\"";
		return Mono.<Void>error(ex).checkpoint(description + " [ExceptionHandlingWebHandler]");
	}
}

它什么都不处理,只做一件事:用 Reactor 的 checkpoint() 给错误序列打上一个"发生在哪个 URL"的标记。Reactor 的 checkpoint 类似同步栈里的断点——异常发生在深层 operator 里时,默认堆栈会丢失上下文;打上 checkpoint 后,错误日志里能直接看到 HTTP "GET /users" [ExceptionHandlingWebHandler]。类注释强调它 "must be the first",就是为了抢在任何处理器把异常转成正常响应之前先打标记。

  1. handle 方法的循环是"用 onErrorResume 层层包裹"。设异常处理器列表为 [H0, H1, H2],循环执行完,序列的语义等价于:
delegate.handle(exchange)
  .onErrorResume(e -> H0.handle(exchange, e))   // H0 处理不了就再向上抛
  .onErrorResume(e -> H1.handle(exchange, e))
  .onErrorResume(e -> H2.handle(exchange, e))

每个 WebExceptionHandler.handle 返回的 Mono 如果自己也是 Mono.error(ex),就代表"我处理不了",于是交给下一个;如果返回完成的 Mono(比如写了 404/500 响应体),错误就此终结。默认配置下列表里只有 CheckpointInsertingHandler + WebFluxResponseStatusExceptionHandler 两个——后者只认 ResponseStatusException,其余异常一路向上漏到 HttpWebHandlerAdapter.handleUnresolvedError 写 500。

  1. doOnError 把错误塞进 exchange 属性(HANDLED_WEB_EXCEPTION 常量,第 46 行)。这个属性是给 observation 用的:HttpWebHandlerAdapter.ObservationSignalListener.doOnComplete(第 418-427 行)会读取它,把"被处理器处理过的错误"记为 observation 的 error——这样监控指标里能区分"未处理错误"与"已处理错误"。
  2. try/catch 同步兜底:super.handle(exchange) 调用本身如果同步抛异常(虽然响应式编程里少见),立刻包装成 Mono.error(ex) 再进入循环——保证进入循环的一定是个 Mono。

19.3.9 FilteringWebHandler 与 DefaultWebFilterChain:过滤器链的不可变链表

FilteringWebHandler(spring-web/src/main/java/org/springframework/web/server/handler/FilteringWebHandler.java)本体极简:

public class FilteringWebHandler extends WebHandlerDecorator {

	private final DefaultWebFilterChain chain;

	public FilteringWebHandler(WebHandler handler, List<WebFilter> filters) {
		super(handler);
		this.chain = new DefaultWebFilterChain(handler, filters);
	}

	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		return this.chain.filter(exchange);
	}
}

真正的设计在 DefaultWebFilterChain(同目录,第 62-113 行):

public DefaultWebFilterChain(WebHandler handler, List<WebFilter> filters) {
	Assert.notNull(handler, "WebHandler is required");
	this.allFilters = Collections.unmodifiableList(filters);
	this.handler = handler;
	DefaultWebFilterChain chain = initChain(filters, handler);
	this.currentFilter = chain.currentFilter;
	this.chain = chain.chain;
}

private static DefaultWebFilterChain initChain(List<WebFilter> filters, WebHandler handler) {
	DefaultWebFilterChain chain = new DefaultWebFilterChain(filters, handler, null, null);
	ListIterator<? extends WebFilter> iterator = filters.listIterator(filters.size());
	while (iterator.hasPrevious()) {
		chain = new DefaultWebFilterChain(filters, handler, iterator.previous(), chain);
	}
	return chain;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange) {
	return Mono.defer(() ->
			this.currentFilter != null && this.chain != null ?
					invokeFilter(this.currentFilter, this.chain, exchange) :
					this.handler.handle(exchange));
}

private Mono<Void> invokeFilter(WebFilter filter, DefaultWebFilterChain chain, ServerWebExchange exchange) {
	return filter.filter(exchange, chain).checkpoint(filter.toString());
}

逐段解读——这是本章最精巧的一段代码:

  1. 链式初始化用 ListIterator 反向遍历。设 filters = [F1, F2, F3],initChain 从一个"空链"(currentFilter=null)开始,反向依次包上 F3、F2、F1,最终结构是:
链头: F1 的节点
  currentFilter = F1, chain = (F2 的节点)
    currentFilter = F2, chain = (F3 的节点)
      currentFilter = F3, chain = (空节点)
        currentFilter = null → filter() 时调用 handler.handle(exchange)

也就是说,每个 DefaultWebFilterChain 实例代表链中的一环,首实例持有 F1 与"剩余链"。这种递归链表让 WebFilter 的放行语义非常自然:过滤器实现里写 chain.filter(exchange) 就等于"调用下一环";不调用 chain.filter(exchange) 就等于短路整个剩余链(例如鉴权失败直接写 401 返回)。 2. filter() 用 Mono.defer 包裹。这是必要的:filter(exchange) 方法被调用时,是在装配期还是运行期不确定;defer 把"决定调哪个 filter/handler"推迟到订阅时刻,保证每个请求都重新走一遍链。 3. 每环都打 checkpoint(invokeFilter 第 111 行)——与 CheckpointInsertingHandler 同理,出错时能在日志里看到是哪个过滤器出的错。 4. 类注释的明确承诺(第 39-41 行):"This class is immutable and thread-safe. It can be created once and re-used to handle request concurrently." 整个链在应用启动时构建一次,之后成千上万个并发请求共享同一个链对象——状态全部在 ServerWebExchange 里,链本身无状态。这与 Servlet 过滤器链"每次请求新建 ApplicationFilterChain"形成对比,也是 WebFlux 高并发的基础之一。

19.3 源码剖析(三):DispatcherHandler——响应式版的前端控制器

DispatcherHandler(spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java)对应 Servlet 世界的 DispatcherServlet,但去掉了一切同步语义。它实现 WebHandler——也就是说,链的最内层终点就是它。

19.3.10 四个策略接口与 initStrategies

DispatcherHandler 把一切可变行为抽象成三个策略列表(第 74-78 行):

private @Nullable List<HandlerMapping> handlerMappings;
private @Nullable List<HandlerAdapter> handlerAdapters;
private @Nullable List<HandlerResultHandler> resultHandlers;

对应三个接口(同包):

  • HandlerMapping:Mono<Object> getHandler(ServerWebExchange exchange)——把请求映射成一个 handler 对象(可以是 HandlerMethod、HandlerFunction、资源处理器等);
  • HandlerAdapter:boolean supports(Object handler) + Mono<HandlerResult> handle(ServerWebExchange, Object)——判断能否调用该 handler,并产出调用结果;
  • HandlerResultHandler:boolean supports(HandlerResult) + Mono<Void> handleResult(ServerWebExchange, HandlerResult)——把返回值渲染/写回响应。

策略收集在 initStrategies(第 115-134 行):

protected void initStrategies(ApplicationContext context) {
	Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerMapping.class, true, false);

	ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
	AnnotationAwareOrderComparator.sort(mappings);
	this.handlerMappings = Collections.unmodifiableList(mappings);

	Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerAdapter.class, true, false);

	this.handlerAdapters = new ArrayList<>(adapterBeans.values());
	AnnotationAwareOrderComparator.sort(this.handlerAdapters);

	Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerResultHandler.class, true, false);

	this.resultHandlers = new ArrayList<>(beans.values());
	AnnotationAwareOrderComparator.sort(this.resultHandlers);
}

三个策略列表全部从容器里按类型收集并按 order 排序。默认 @EnableWebFlux 下的实际装配(来自 WebFluxConfigurationSupport):

  • HandlerMapping 列表:RouterFunctionMapping(order=-1,函数式优先)→ RequestMappingHandlerMapping(order=0)→ 静态资源 resourceHandlerMapping;
  • HandlerAdapter 列表:RequestMappingHandlerAdapter、HandlerFunctionAdapter、SimpleHandlerAdapter、WebSocketHandlerAdapter;
  • HandlerResultHandler 列表:ResponseEntityResultHandler、ServerResponseResultHandler、ViewResolutionResultHandler、ResponseBodyResultHandler(order=100,兜底最后)。

注意策略列表是"列表"而不是"最优匹配一个":handler mapping 按顺序逐个尝试,第一个返回非空 handler 的胜出;adapter 与 resultHandler 也是按顺序找第一个 supports 返回 true 的。

19.3.11 handle() 逐行拆解:L4 的主流程

handle(ServerWebExchange)(第 138-151 行)全文:

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	if (this.handlerMappings == null) {
		return createNotFoundError();
	}
	if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
		return handlePreFlight(exchange);
	}
	return Flux.fromIterable(this.handlerMappings)
			.concatMap(mapping -> mapping.getHandler(exchange))
			.next()
			.switchIfEmpty(createNotFoundError())
			.onErrorResume(ex -> handleResultMono(exchange, Mono.error(ex)))
			.flatMap(handler -> handleRequestWith(exchange, handler));
}

逐行拆解——这是全章最值得逐符号读懂的一段:

  1. handlerMappings == null:说明 initStrategies 没跑过(DispatcherHandler 没经过 setApplicationContext 就被调用),直接 404。
  2. CORS 预检短路:OPTIONS 请求且带 Origin/Access-Control-Request-Method 头时走 handlePreFlight(第 212-218 行)——同样遍历 mappings 找 handler 取 CORS 配置,找不到就直接 setStatusCode(FORBIDDEN)。
  3. Flux.fromIterable(this.handlerMappings).concatMap(mapping -> mapping.getHandler(exchange)):把 mapping 列表转成 Flux,对每个 mapping 调 getHandler。concatMap 保持顺序且串行执行——前一个 mapping 的 Mono 完成才订阅下一个。
  4. .next():取第一个非空元素。响应式里"匹配不到"的 mapping 返回 Mono.empty()(不发射元素),next() 直接跳过空序列拿到第一个有 handler 的。
  5. .switchIfEmpty(createNotFoundError()):所有 mapping 都返回空(没一个匹配)→ 走 createNotFoundError()(第 153-158 行):
private <R> Mono<R> createNotFoundError() {
	return Mono.defer(() -> {
		Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND);
		return Mono.error(ex);
	});
}

没匹配到 handler 时不写响应,而是发出一个 ResponseStatusException(404) 的 error 信号——这个错误会向上冒泡到 ExceptionHandlingWebHandler,由 WebFluxResponseStatusExceptionHandler 把它翻译成 404 状态码。这是响应式错误模型的典型用法:"未找到"被建模为错误信号,而不是手工 setStatusCode(404)。 6. .onErrorResume(ex -> handleResultMono(exchange, Mono.error(ex))):关键一行。匹配阶段如果出错(比如某个 mapping 抛异常),用 handleResultMono 包装这个错误 Mono——这样,连"选 handler 之前的错误"也能走 @ExceptionHandler 处理。这依赖 RequestMappingHandlerAdapter 实现了 DispatchExceptionHandler 接口(见 19.3.13)。 7. .flatMap(handler -> handleRequestWith(exchange, handler)):拿到 handler,进入调用阶段。

19.3.12 handleRequestWith 与 handleResult:adapter 与 resultHandler 的两跳

private Mono<Void> handleRequestWith(ServerWebExchange exchange, Object handler) {
	if (ObjectUtils.nullSafeEquals(exchange.getResponse().getStatusCode(), HttpStatus.FORBIDDEN)) {
		return Mono.empty();  // CORS rejection
	}
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				Mono<HandlerResult> resultMono = adapter.handle(exchange, handler);
				return handleResultMono(exchange, resultMono);
			}
		}
	}
	return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}
  • CORS 拒绝的短路:若 mapping 阶段已经把状态码置为 403(AbstractHandlerMapping 的 CORS 拒绝逻辑),直接完成、不再调用 handler。
  • 遍历 adapter 列表找 supports(handler) 为 true 的那个;adapter.handle(exchange, handler) 返回 Mono<HandlerResult>,立刻交给 handleResultMono 包装。
  • 找不到 adapter 是编程错误(没有对应类型的 adapter),发 IllegalStateException 错误信号。

handleResultMono(第 160-179 行):

private Mono<Void> handleResultMono(ServerWebExchange exchange, Mono<HandlerResult> resultMono) {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter instanceof DispatchExceptionHandler exceptionHandler) {
				resultMono = resultMono.onErrorResume(ex2 -> exceptionHandler.handleError(exchange, ex2));
			}
		}
	}
	return resultMono.flatMap(result -> {
		Mono<Void> voidMono = handleResult(exchange, result, "Handler " + result.getHandler());
		DispatchExceptionHandler exceptionHandler = result.getExceptionHandler();
		if (exceptionHandler != null) {
			voidMono = voidMono.onErrorResume(ex ->
					exceptionHandler.handleError(exchange, ex).flatMap(result2 ->
							handleResult(exchange, result2, "Exception handler " +
									result2.getHandler() + ", error=\"" + ex.getMessage() + "\"")));
		}
		return voidMono;
	});
}

两段逻辑:

  1. 给 resultMono 挂上"全局"异常处理:每个实现 DispatchExceptionHandler 的 adapter(即 RequestMappingHandlerAdapter)都把自己的 handleError 以 onErrorResume 叠上去——这就是"handler 调用前/调用时的错误能走 @ExceptionHandler"的机制来源。
  2. 给"渲染阶段"也挂异常处理:HandlerResult 自身可以携带一个 exceptionHandler(HandlerResult.setExceptionHandler,注解模型下由 RequestMappingHandlerAdapter 注入)。渲染(handleResult)失败时,先用这个 exceptionHandler 产出一个新的 HandlerResult(即 @ExceptionHandler 方法的返回值),再对它做一次 handleResult——这就是"异常处理器的返回值也能像正常返回值一样写回"的闭环。

最后 handleResult(第 181-194 行):

private Mono<Void> handleResult(
		ServerWebExchange exchange, HandlerResult handlerResult, String description) {

	if (this.resultHandlers != null) {
		for (HandlerResultHandler resultHandler : this.resultHandlers) {
			if (resultHandler.supports(handlerResult)) {
				description += " [DispatcherHandler]";
				return resultHandler.handleResult(exchange, handlerResult).checkpoint(description);
			}
		}
	}
	return Mono.error(new IllegalStateException(
			"No HandlerResultHandler for " + handlerResult.getReturnValue()));
}

按 order 遍历 resultHandler 列表,第一个 supports 为 true 的执行 handleResult,并打 checkpoint。至此 DispatcherHandler 的全部职责完成:它自己不认识任何业务,只做"找 handler → 调 adapter → 找 resultHandler 渲染"三件事。

19.3 源码剖析(四):注解编程模型——从 @RequestMapping 到方法调用

19.3.13 RequestMappingHandlerMapping:扫描期建立映射表

响应式版的 RequestMappingHandlerMapping 在 spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java。它继承 RequestMappingInfoHandlerMapping(再往上是 AbstractHandlerMethodMapping、AbstractHandlerMapping),核心是两个模板方法的实现。

isHandler:什么样的 bean 算控制器(第 156-158 行):

@Override
protected boolean isHandler(Class<?> beanType) {
	return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}

类或类的元注解层级上有 @Controller 即入选(注意 @RestController 是 @Controller 的组合注解,所以也算)。响应式版与 Servlet 版此处语义一致,区别在后续的匹配引擎:响应式版用 PathPattern(getPathPatternParser())而不是 Ant 路径模式。

getMappingForMethod:把注解转成 RequestMappingInfo(第 178-203 行):

@Override
protected @Nullable RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	validateCglibProxyMethodVisibility(method, handlerType);

	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			info = typeInfo.combine(info);
		}
		if (info.getPatternsCondition().isEmptyPathMapping()) {
			info = info.mutate().paths("", "/").options(this.config).build();
		}
		for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
			if (entry.getValue().test(handlerType)) {
				String prefix = entry.getKey();
				if (this.embeddedValueResolver != null) {
					prefix = this.embeddedValueResolver.resolveStringValue(prefix);
				}
				Assert.state(prefix != null, "Prefix must not be null");
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
				break;
			}
		}
	}
	return info;
}

流程:方法上有 @RequestMapping(或 6.1+ 新增的 @HttpExchange)才生成 info;然后与类级 @RequestMapping 的 info 做 combine(类路径 + 方法路径合并);空路径映射补成 "" 与 "/" 两个模式;最后应用 setPathPrefixes 配置的全局前缀(如把 @RestController 类统一挂到 /api/**)。

7.0 新增的 CGLIB 可见性校验(validateCglibProxyMethodVisibility,第 209-235 行):被 AOP 代理(CGLIB 子类)增强的控制器里,private 方法不能被重写、跨包的包级私有方法实际上不可见——7.0.9 在扫描期直接抛 IllegalStateException 报错,而不是等到运行时 404。这是与旧版本的一个明确差异,属于"启动失败优于运行时诡异行为"的改进。

运行期匹配走父类 AbstractHandlerMapping.getHandler(spring-webflux/.../handler/AbstractHandlerMapping.java 第 187 行起):initApiVersion(exchange) 后调 getHandlerInternal(exchange)(子类实现,响应式版在 RequestMappingInfoHandlerMapping 里按 PathPattern 匹配),命中后做 CORS 配置组合校验,最终把 BEST_MATCHING_PATTERN_ATTRIBUTE、URI_TEMPLATE_VARIABLES_ATTRIBUTE 等写进 exchange 属性。返回的 handler 对象类型是 HandlerMethod(封装 bean + Method)。

19.3.14 RequestMappingHandlerAdapter.handle:调用前的组装

RequestMappingHandlerAdapter(.../result/method/annotation/RequestMappingHandlerAdapter.java)实现 HandlerAdapter 且实现 DispatchExceptionHandler。supports 极其简单(第 248-250 行):

@Override
public boolean supports(Object handler) {
	return handler instanceof HandlerMethod;
}

核心 handle(第 253-282 行):

@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {

	Assert.state(this.methodResolver != null &&
			this.modelInitializer != null && this.reactiveAdapterRegistry != null, "Not initialized");

	HandlerMethod handlerMethod = (HandlerMethod) handler;

	InitBinderBindingContext bindingContext = new InitBinderBindingContext(
			this.webBindingInitializer, this.methodResolver.getInitBinderMethods(handlerMethod),
			this.methodResolver.hasMethodValidator() && handlerMethod.shouldValidateArguments(),
			this.reactiveAdapterRegistry);

	InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod);

	DispatchExceptionHandler exceptionHandler =
			(exchange2, ex) -> handleException(exchange, ex, handlerMethod, bindingContext);

	Mono<HandlerResult> resultMono = this.modelInitializer
			.initModel(handlerMethod, bindingContext, exchange)
			.then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)))
			.doOnNext(result -> result.setExceptionHandler(exceptionHandler))
			.onErrorResume(ex -> exceptionHandler.handleError(exchange, ex));

	Scheduler optionalScheduler = this.methodResolver.getSchedulerFor(handlerMethod);
	if (optionalScheduler != null) {
		return resultMono.subscribeOn(optionalScheduler);
	}

	return resultMono;
}

逐段拆解:

  1. InitBinderBindingContext:每个请求新建一个绑定上下文——持有 @InitBinder 方法列表(全局 @ControllerAdvice 的 + 控制器本地的)与 WebBindingInitializer。数据绑定(form/query 参数到命令对象)发生在这个上下文里。
  2. methodResolver.getRequestMappingMethod(handlerMethod):把 HandlerMethod 包装成 InvocableHandlerMethod,并注入全套参数解析器(见 19.3.15)。
  3. modelInitializer.initModel(...):先执行 @ModelAttribute 方法与 session 属性初始化模型——这是"方法调用前的准备",全部异步化(返回 Mono)。
  4. Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext)):真正的方法调用被 defer,订阅时才执行。
  5. doOnNext(result -> result.setExceptionHandler(exceptionHandler)):调用成功拿到 HandlerResult 后,把异常处理回调(handleException)塞进结果——DispatcherHandler.handleResultMono 渲染阶段就用它(见 19.3.12)。
  6. onErrorResume(ex -> exceptionHandler.handleError(exchange, ex)):调用期错误直接走 handleException——即 @ExceptionHandler 解析。
  7. 阻塞方法的调度器:6.1 新增的 setBlockingExecutor 机制。若配置了 blockingExecutor 且当前方法被 blockingMethodPredicate 判定为阻塞型(默认实现 NonReactiveHandlerMethodPredicate:返回类型不被 ReactiveAdapterRegistry 识别、且不是 Kotlin 挂起函数),则整条 resultMono 用 subscribeOn(optionalScheduler) 切到专用线程池执行——避免在 Netty 事件循环线程上跑阻塞业务。

19.3.15 InvocableHandlerMethod.invoke:参数解析与反射调用

InvocableHandlerMethod(spring-webflux/.../result/method/InvocableHandlerMethod.java)是"可调用的控制器方法"。参数解析主循环 getMethodArgumentValues(第 250-282 行):

private Mono<Object[]> getMethodArgumentValues(
		ServerWebExchange exchange, BindingContext bindingContext, @Nullable Object... providedArgs) {

	MethodParameter[] parameters = getMethodParameters();
	if (ObjectUtils.isEmpty(parameters)) {
		return EMPTY_ARGS;
	}

	List<Mono<Object>> argMonos = new ArrayList<>(parameters.length);
	for (MethodParameter parameter : parameters) {
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		Object providedArg = findProvidedArgument(parameter, providedArgs);
		if (providedArg != null) {
			argMonos.add(Mono.just(providedArg));
			continue;
		}
		if (!this.resolvers.supportsParameter(parameter)) {
			return Mono.error(new IllegalStateException(
					formatArgumentError(parameter, "No suitable resolver")));
		}
		try {
			argMonos.add(this.resolvers.resolveArgument(parameter, bindingContext, exchange)
					.defaultIfEmpty(NO_ARG_VALUE)
					.doOnError(ex -> logArgumentErrorIfNecessary(exchange, parameter, ex)));
		}
		catch (Exception ex) {
			logArgumentErrorIfNecessary(exchange, parameter, ex);
			argMonos.add(Mono.error(ex));
		}
	}
	return Mono.zip(argMonos, values ->
			Stream.of(values).map(value -> value != NO_ARG_VALUE ? value : null).toArray());
}

要点:

  1. 每个参数一个 Mono<Object>:响应式模型下,解析参数本身可能是异步的——最典型的是 @RequestBody,要等请求体流收完、Jackson 解码完成才能拿到值。所以参数值不是同步算出来的,而是一串 Mono。
  2. Mono.zip 聚合:所有参数 Mono 全部就绪(并行订阅),才组合成 Object[] 去反射调用。参数之间互不阻塞——@RequestParam("id") 同步解析与 @RequestBody 异步解码同时进行。
  3. defaultIfEmpty(NO_ARG_VALUE):解析器返回空(参数缺失且非 required)时用哨兵占位,最终转成 null。
  4. 找不到 resolver 直接报错:No suitable resolver——常见于自定义参数类型没注册对应 HandlerMethodArgumentResolver。

拿到参数数组后的反射调用(invoke 方法第 184-241 行片段):

Object value;
……
value = method.invoke(getBean(), args);
……
HttpStatusCode status = getResponseStatus();
if (status != null) {
	exchange.getResponse().setStatusCode(status);
}

MethodParameter returnType = getReturnType();
if (isResponseHandled(args, exchange)) {
	……
}

HandlerResult result = new HandlerResult(this, value, returnType, bindingContext);
return Mono.just(result);
  • InvocationTargetException 被拆包:控制器方法抛出的业务异常从这里以 error 信号进入响应式链(Mono.error(ex.getTargetException()))——这就是 @ExceptionHandler 能接住业务异常的原因。
  • 方法上的 @ResponseStatus 在调用后立刻写状态码。
  • isResponseHandled 判定"响应是否已被方法自己处理完"(第 310-320 行):
private boolean isResponseHandled(Object[] args, ServerWebExchange exchange) {
	if (getResponseStatus() != null || exchange.isNotModified()) {
		return true;
	}
	for (Object arg : args) {
		if (arg instanceof ServerHttpResponse || arg instanceof ServerWebExchange) {
			return true;
		}
	}
	return false;
}

如果方法参数里直接注入了 ServerHttpResponse/ServerWebExchange,说明用户自己动手写响应了,此时返回值不再包装成 HandlerResult(避免 resultHandler 二次写回)。这是与 Servlet 模型"返回 void 且直接写 response 表示自己处理完"同款语义。

19.3.16 参数解析器清单:7.0.9 仓库实测

ControllerMethodResolver.initResolvers(.../result/method/annotation/ControllerMethodResolver.java 第 202-260 行)是 WebFlux 版内置解析器的权威清单(按在列表中的顺序,同一份列表服务于 @RequestMapping 方法;initBinder/@ModelAttribute/@ExceptionHandler 用裁剪版):

List<HandlerMethodArgumentResolver> result = new ArrayList<>(30);
// 注解型
result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, false));   // @RequestParam
result.add(new RequestParamMapMethodArgumentResolver(adapterRegistry));                 // @RequestParam Map
result.add(new PathVariableMethodArgumentResolver(beanFactory, adapterRegistry));        // @PathVariable
result.add(new PathVariableMapMethodArgumentResolver(adapterRegistry));                 // @PathVariable Map
result.add(new MatrixVariableMethodArgumentResolver(beanFactory, adapterRegistry));      // @MatrixVariable
result.add(new MatrixVariableMapMethodArgumentResolver(adapterRegistry));
if (!readers.isEmpty()) {
	result.add(new RequestBodyMethodArgumentResolver(readers, adapterRegistry));         // @RequestBody
	result.add(new RequestPartMethodArgumentResolver(readers, adapterRegistry));         // multipart @RequestPart
}
if (supportDataBinding) {
	result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, false));        // @ModelAttribute
}
result.add(new RequestHeaderMethodArgumentResolver(beanFactory, adapterRegistry));       // @RequestHeader
result.add(new RequestHeaderMapMethodArgumentResolver(adapterRegistry));
result.add(new CookieValueMethodArgumentResolver(beanFactory, adapterRegistry));         // @CookieValue
result.add(new ExpressionValueMethodArgumentResolver(beanFactory, adapterRegistry));    // @Value 表达式
result.add(new SessionAttributeMethodArgumentResolver(beanFactory, adapterRegistry));    // @SessionAttribute
result.add(new RequestAttributeMethodArgumentResolver(beanFactory, adapterRegistry));   // @RequestAttribute
// 类型型
if (!readers.isEmpty()) {
	result.add(new HttpEntityMethodArgumentResolver(readers, adapterRegistry));           // HttpEntity<?>
}
result.add(new ModelMethodArgumentResolver(adapterRegistry));                           // Model
if (supportDataBinding) {
	result.add(new ErrorsMethodArgumentResolver(adapterRegistry));                       // BindingResult
}
result.add(new ServerWebExchangeMethodArgumentResolver(adapterRegistry));                // ServerWebExchange
result.add(new PrincipalMethodArgumentResolver(adapterRegistry));                         // java.security.Principal
if (requestMappingMethod) {
	result.add(new SessionStatusMethodArgumentResolver());                               // SessionStatus
}
result.add(new WebSessionMethodArgumentResolver(adapterRegistry));                      // WebSession
result.add(new ApiVersionMethodArgumentResolver());                                     // 7.0 新增:API 版本
if (KotlinDetector.isKotlinPresent()) {
	result.add(new ContinuationHandlerMethodArgumentResolver());                           // Kotlin 挂起函数 Continuation
}
// 用户自定义
result.addAll(customResolvers.getCustomResolvers());
// 兜底型
result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, true));  // 无注解简单类型兜底
if (supportDataBinding) {
	result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, true));          // POJO 兜底
}

按 supports 判定的先后顺序解释这张表的设计:注解型在前(带明确注解的参数优先匹配专属解析器),类型型居中(按参数类型识别,如直接声明 ServerWebExchange exchange),兜底型在最后——RequestParamMethodArgumentResolver(true) 那个 true 表示"是否兜底模式":任何没注解的简单类型(String/int 等)都当请求参数处理;ModelAttributeMethodArgumentResolver(true) 兜底剩下的 POJO(命令对象绑定)。用户自定义解析器插在兜底之前,因此优先级高于兜底、低于内置注解型。

19.3 源码剖析(五):返回值写回——ResponseBodyResultHandler 与 writeWith 的终点

19.3.17 supports 判定:谁来处理 @ResponseBody 返回值

ResponseBodyResultHandler(spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseBodyResultHandler.java)的 supports 逻辑(第 99-104 行):

@Override
public boolean supports(HandlerResult result) {
	MethodParameter returnType = result.getReturnTypeSource();
	return (this.responseBodyControllerCache.computeIfAbsent(returnType.getContainingClass(),
			clazz -> AnnotatedElementUtils.hasAnnotation(clazz, ResponseBody.class)) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

判定规则:控制器类上有 @ResponseBody(@RestController 即其组合注解),或方法上有 @ResponseBody。用 ConcurrentHashMap 缓存类级判定结果——类是否带注解对所有方法恒定,避免每次请求反射。

注意它在 resultHandler 列表里的 order=100(构造器里 setOrder(100)),是最后一个兜底的 resultHandler。排在它前面的 ResponseEntityResultHandler(order 默认 0 附近)优先识别 ResponseEntity 返回值——所以 ResponseEntity<User> 不会被它抢走。这就是为什么 @ResponseBody 注释里说"it does recognize and explicitly ignores the ResponseEntity return type":子类优先,它兜底。

19.3.18 handleResult 与 writeBody:从业务对象到 writeWith

handleResult(同文件第 107-119 行):

@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
	Object body = result.getReturnValue();
	MethodParameter bodyTypeParameter = result.getReturnTypeSource();
	if (body instanceof ProblemDetail detail) {
		exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
		if (detail.getInstance() == null) {
			URI path = URI.create(exchange.getRequest().getPath().value());
			detail.setInstance(path);
		}
		invokeErrorResponseInterceptors(detail, null);
	}
	return writeBody(body, bodyTypeParameter, exchange);
}

ProblemDetail(RFC 9457 问题详情对象)有特殊待遇:状态码从 detail 里取、instance 缺省填请求路径。其余情况全部委托给父类 AbstractMessageWriterResultHandler.writeBody。

writeBody(.../AbstractMessageWriterResultHandler.java 第 169-254 行)是写回路径的总装车间,核心片段:

ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
ResolvableType actualType = (actualParam != null ? ResolvableType.forMethodParameter(actualParam) : bodyType);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve(), body);

Publisher<?> publisher;
ResolvableType elementType;
if (adapter != null) {
	publisher = adapter.toPublisher(body);
	……
	elementType = getElementType(adapter, genericType);
}
else {
	publisher = Mono.justOrEmpty(body);
	……
}

if (ClassUtils.isVoidType(elementType.resolve())) {
	return Mono.from((Publisher<Void>) publisher);
}

MediaType bestMediaType;
try {
	bestMediaType = selectMediaType(exchange, () -> getMediaTypesFor(elementType));
}
catch (NotAcceptableStatusException ex) {
	……
}

if (bestMediaType != null) {
	for (HttpMessageWriter<?> writer : getMessageWriters()) {
		if (writer.canWrite(actualElementType, bestMediaType)) {
			return writer.write((Publisher) publisher, actualType, elementType,
					bestMediaType, exchange.getRequest(), exchange.getResponse(),
					Hints.from(Hints.LOG_PREFIX_HINT, logPrefix));
		}
	}
}

逐段拆解:

  1. ReactiveAdapterRegistry.getAdapter(bodyType.resolve(), body):返回值若是 Mono<User>、Flux<User> 这类响应式类型,adapter 把它转成 Publisher<?>;否则包成 Mono.justOrEmpty(body)。业务方法返回 Mono<User> 时,body 此时还没解析——publisher 是延迟的。
  2. elementType 剥壳:从 Mono<User> 里推出元素类型 User——这才是消息转换器要序列化的对象类型。
  3. selectMediaType(exchange, ...):根据 Accept 头与元素类型可写的 media type 做内容协商,选出 bestMediaType。协商失败(没有可接受的)抛 NotAcceptableStatusException(406)。
  4. 遍历 HttpMessageWriter 列表找 canWrite(elementType, bestMediaType) 的那个,调用 writer.write(publisher, ...)。典型实现是 Jackson2JsonEncoder(spring-core/spring-web 的 codec 包):它订阅 publisher,每个元素用 Jackson 序列化成 DataBuffer,最终链到 ServerHttpResponse.writeWith(publisher)——19.3.2 节的 writeWith 在这里被真正调用。

至此写回路径的完整链条闭合:

控制器返回 Mono<User>
  → writeBody: adapter.toPublisher(body)
    → Jackson2JsonEncoder 订阅 publisher,逐元素编码为 DataBuffer 流
      → ServerHttpResponse.writeWith(Flux<DataBuffer>)
        → ReactorServerHttpResponse → Netty channel 写出

19.3.19 其余三个 resultHandler 的分工

  • ResponseEntityResultHandler(同包):supports 判定返回值是 ResponseEntity 实例。它比 ResponseBodyResultHandler 多做一件事——从 ResponseEntity 里取出状态码与 header,先写进 exchange.getResponse(),再把 body 部分走一遍与 writeBody 相同的流程。所以 return ResponseEntity.status(404).body(...) 与 return Mono.notFound() 等价的语义在此落地。
  • ViewResolutionResultHandler(spring-webflux/.../result/view/ViewResolutionResultHandler.java):supports 判定返回值是视图类型(String 视图名、View 实例、Rendering 模型)。它把模型交给 ViewResolver 链解析出 View,再调 view.render(...)——响应式模板渲染(FreeMarker 等)走这里。API 服务的纯 JSON 返回值不会命中它。
  • ServerResponseResultHandler(.../function/server/support/ServerResponseResultHandler.java):supports 判定返回值是 ServerResponse 实例——函数式编程模型专用,下一节展开。

19.3 源码剖析(六):函数式编程模型——RouterFunction 与 HandlerFunction

19.3.20 两个核心函数式接口

函数式模型的两个原语(spring-webflux/src/main/java/org/springframework/web/reactive/function/server/):

// HandlerFunction.java
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
	Mono<T> handle(ServerRequest request);
}

// RouterFunction.java(接口本体)
public interface RouterFunction<T extends ServerResponse> {
	Mono<HandlerFunction<T>> route(ServerRequest request);
	……
}
  • HandlerFunction:就是"处理请求的函数",输入 ServerRequest(对 ServerWebExchange 的不可读视图 + body 提取器),输出 Mono<ServerResponse>。
  • RouterFunction:"判断该把请求交给哪个 HandlerFunction 的函数",route(request) 返回 Mono<HandlerFunction>——匹配不到就是 empty。

注意 route 返回 Mono:匹配过程本身也可以是异步的(例如需要查询路由表)。

19.3.21 RouterFunctions:路由的拼装方式

RouterFunctions 工具类(.../function/server/RouterFunctions.java)提供三种入口。

静态 route(第 116-120 行):

public static <T extends ServerResponse> RouterFunction<T> route(
		RequestPredicate predicate, HandlerFunction<T> handlerFunction) {

	return new DefaultRouterFunction<>(predicate, handlerFunction);
}

一个谓词 + 一个处理器 = 一条路由。RequestPredicates.GET("/user") 这类工厂方法产出谓词。

嵌套路由 nest(第 146-150 行):

public static <T extends ServerResponse> RouterFunction<T> nest(
		RequestPredicate predicate, RouterFunction<T> routerFunction) {

	return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}

共享前缀/公共谓词的一组路由套一层。多路由之间用 .andRoute(...)、.andOther(...) 组合(RouterFunction 接口的 default 方法)。

builder DSL(第 97-99 行):

public static Builder route() {
	return new RouterFunctionBuilder();
}

即 RouterFunctions.route().GET("/user", controller::listUsers).POST("/user", controller::createUser).build() 这种流式写法。

19.3.22 RouterFunctionMapping:把 RouterFunction 接入 DispatcherHandler

函数式模型要复用 DispatcherHandler 的分发,就得有一个 HandlerMapping 实现——RouterFunctionMapping(.../function/server/support/RouterFunctionMapping.java)。

afterPropertiesSet(第 98-117 行):没显式给 routerFunction 时,从容器收集所有 RouterFunction bean,用 reduce(RouterFunction::andOther) 合并成一条(第 122-126 行):

protected void initRouterFunctions() {
	List<RouterFunction<?>> routerFunctions = routerFunctions();
	this.routerFunction = routerFunctions.stream().reduce(RouterFunction::andOther).orElse(null);
	logRouterFunctions(routerFunctions);
}

运行期匹配(getHandlerInternal,第 159-166 行):

@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
	if (this.routerFunction == null) {
		return Mono.empty();
	}
	ServerRequest request = ServerRequest.create(exchange, this.messageReaders, getApiVersionStrategy());
	return this.routerFunction.route(request)
			.doOnNext(handler -> setAttributes(exchange.getAttributes(), request, handler));
}

两步:先 ServerRequest.create(exchange, messageReaders, ...) 把 exchange 包成函数式世界的请求视图(body 提取靠 messageReaders),再 routerFunction.route(request)。匹配成功后 setAttributes 把 ServerRequest 与匹配到的 PathPattern 等写回 exchange 属性——关键是把 ServerRequest 存进 exchange 属性(RouterFunctions.REQUEST_ATTRIBUTE),因为下一步 HandlerFunctionAdapter 要从这里取回它。

回顾 19.3.10 提到的默认顺序:RouterFunctionMapping order=-1,优先于 RequestMappingHandlerMapping(order=0)。也就是说同一应用里函数式路由先试、注解后试。

19.3.23 HandlerFunctionAdapter 与 ServerResponseResultHandler

HandlerFunctionAdapter(.../support/HandlerFunctionAdapter.java)全文核心(第 53-63 行):

@Override
public boolean supports(Object handler) {
	return handler instanceof HandlerFunction;
}

@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
	HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
	ServerRequest request = exchange.getRequiredAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
	return handlerFunction.handle(request)
			.map(response -> new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE));
}
  • supports:handler 是 HandlerFunction 实例(lambda 天然实现该接口)。
  • 从 exchange 属性取回 ServerRequest,调用 handlerFunction.handle(request) 得到 Mono<ServerResponse>,.map 成 HandlerResult——返回值类型是写死的 HandlerFunction.handle 方法签名(静态块里预建的 MethodParameter)。

ServerResponseResultHandler(同目录)的 supports(第 105-107 行)判定返回值是 ServerResponse 实例,handleResult(第 110-123 行):

@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
	ServerResponse response = (ServerResponse) result.getReturnValue();
	Assert.state(response != null, "No ServerResponse");
	return response.writeTo(exchange, new ServerResponse.Context() {
		@Override
		public List<HttpMessageWriter<?>> messageWriters() {
			return messageWriters;
		}
		@Override
		public List<ViewResolver> viewResolvers() {
			return viewResolvers;
		}
	});
}

写回动作委托给 ServerResponse.writeTo——响应对象自己知道怎么写自己(状态码、header、body/视图)。这就是函数式模型与注解模型的本质区别:注解模型由 resultHandler 解释返回值;函数式模型由返回值(ServerResponse)自我渲染。ServerResponse 的常见实现(EntityResponse、RenderingResponse、ServerResponse.created(uri).body(...) builder)最终殊途同归,调用的还是 HttpMessageWriter/View,落到同一个 writeWith 终点。

19.3 源码剖析(七):WebClient——响应式 HTTP 客户端

19.3.24 构建与链路结构

WebClient(spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java)是响应式客户端的门面接口。静态入口(第 164-166 行):

static WebClient.Builder builder() {
	return new DefaultWebClientBuilder();
}

常用 builder 方法(接口内声明):Builder baseUrl(String baseUrl)(第 191 行)设置默认根路径、defaultHeader(...)、defaultCookies(...)、Builder filter(ExchangeFilterFunction filter)(第 303 行)、Builder filters(Consumer<List<ExchangeFilterFunction>>)(第 312 行)批量改过滤器列表、exchangeStrategies(...) 配置消息编解码器。

DefaultWebClient(同包 DefaultWebClient.java)的构造器(第 112-133 行)揭示了它的全部静态依赖:

DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable ExchangeFilterFunction filterFunctions,
		UriBuilderFactory uriBuilderFactory, @Nullable HttpHeaders defaultHeaders,
		@Nullable MultiValueMap<String, String> defaultCookies, ……) {

	this.exchangeFunction = exchangeFunction;
	this.filterFunctions = filterFunctions;
	this.uriBuilderFactory = uriBuilderFactory;
	this.defaultHeaders = defaultHeaders;
	this.defaultCookies = defaultCookies;
	……
}

两个关键字段:

  • ExchangeFunction exchangeFunction:最底层的"发请求拿响应"函数(默认实现是 ExchangeFunctions.create(HttpClient),背后是 Reactor Netty 的 HttpClient);
  • ExchangeFilterFunction filterFunctions:所有过滤器合并成的单个函数——builder 里多次 filter(...) 不是存列表,而是通过 ExchangeFilterFunction.andThen 折叠成一条链,最终以 filterFunctions.apply(exchangeFunction) 包在底层函数外面。

请求的发起是惰性的:webClient.get().uri("/users").retrieve() 只构建了一个 DefaultRequestBodyUriSpec(DefaultWebClient 的内部类,第 207 行起),真正发起 HTTP 调用发生在订阅 retrieve/exchange 返回的 Mono 时。

19.3.25 retrieve 与 exchange 的语义差异

两者的区别在 DefaultWebClient 的内部实现里一目了然:

  • retrieve():返回 ResponseSpec(默认实现 DefaultResponseSpec)。它的语义是"声明式":自动对状态码做处理——2xx 正常进入 body 提取;4xx/5xx 默认转成 WebClientResponseException(通过 defaultStatusHandlers:onStatus 注册的谓词触发 Mono.error(...))。也就是说 retrieve 把"HTTP 状态码 ≠ 2xx"建模为 error 信号,下游直接用 onErrorResume 处理。
  • exchange():返回 Mono<ClientResponse>。语义是"原生式":把 ClientResponse(响应的完整视图:状态码、header、body)原样交给调用者,不做任何状态码推断,也不保证 body 被消费——接口注释(WebClient.java 第 790 行附近)明确警告:exchange 模式下如果不对 response 做 body 提取,连接可能泄漏(DefaultWebClient 里有 releaseIfNotConsumed(ClientResponse) 静态方法兜底释放)。

一句话:retrieve 是"框架替你判断成败",exchange 是"你自己看着办"。

19.3.26 ExchangeFilterFunction:请求/响应双向拦截

ExchangeFilterFunction(.../function/client/ExchangeFilterFunction.java)是函数式接口(第 51 行):

Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next);

与服务端 WebFilter 的结构同构:拿到当前 request 与"下一个环节",决定是改写 request 放行、短路返回自定义响应,还是对下游返回的 response 做加工。两个 default 方法(第 59-74 行):

default ExchangeFilterFunction andThen(ExchangeFilterFunction afterFilter) {
	Assert.notNull(afterFilter, "ExchangeFilterFunction must not be null");
	return (request, next) ->
			filter(request, afterRequest -> afterFilter.filter(afterRequest, next));
}

default ExchangeFunction apply(ExchangeFunction exchange) {
	Assert.notNull(exchange, "ExchangeFunction must not be null");
	return request -> this.filter(request, exchange);
}

andThen 把两个过滤器折叠(这正是 DefaultWebClient 把过滤器列表折叠成单个函数的机制);apply 把过滤器套在底层 ExchangeFunction 外。两个静态工厂(第 82-96 行):

static ExchangeFilterFunction ofRequestProcessor(Function<ClientRequest, Mono<ClientRequest>> processor) {
	return (request, next) -> processor.apply(request).flatMap(next::exchange);
}

static ExchangeFilterFunction ofResponseProcessor(Function<ClientResponse, Mono<ClientResponse>> processor) {
	return (request, next) -> next.exchange(request).flatMap(processor);
}

ofRequestProcessor 只加工请求(典型用法:给请求注入鉴权 header、链路追踪 traceparent),ofResponseProcessor 只加工响应。全链路追踪 header 传递就是典型场景:写一个 ofRequestProcessor,从 Reactor Context 里取出 traceId,塞进 request header,再放行。

19.3 源码剖析(八):错误处理体系

19.3.27 两级错误处理的分工

WebFlux 的错误处理是两层结构,与 Servlet 模型差异很大:

第一层(框架层,WebExceptionHandler 链):由 ExceptionHandlingWebHandler 编织,默认注册的只有一个 WebFluxResponseStatusExceptionHandler(spring-webflux/.../handler/WebFluxResponseStatusExceptionHandler.java),它继承 spring-web 的 ResponseStatusExceptionHandler,只重写了 determineStatus(第 43-52 行):

@Override
protected @Nullable HttpStatusCode determineStatus(Throwable ex) {
	HttpStatusCode statusCode = super.determineStatus(ex);
	if (statusCode == null) {
		ResponseStatus ann = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
		if (ann != null) {
			statusCode = ann.code();
		}
	}
	return statusCode;
}

父类 ResponseStatusExceptionHandler(spring-web/.../server/handler/ResponseStatusExceptionHandler.java)的 handle 逻辑(第 65-80 行):determineStatus 能解出状态码 → response.setStatusCode(...)、把 ResponseStatusException.getHeaders() 合并进响应头、然后 exchange.getResponse().setComplete();解不出 → 返回 Mono.error(ex) 继续抛。也就是说这一层只负责"已知状态码的异常翻译",不产生 body、不渲染模板。

第二层(应用层,@ExceptionHandler):由 RequestMappingHandlerAdapter 承担。回顾 19.3.14:handle 里 onErrorResume(ex -> exceptionHandler.handleError(exchange, ex)),而 handleError(第 350-352 行)就是调用私有方法 handleException(第 284-330 行):

private Mono<HandlerResult> handleException(
		ServerWebExchange exchange, Throwable exception,
		@Nullable HandlerMethod handlerMethod, @Nullable BindingContext bindingContext) {

	Assert.state(this.methodResolver != null, "Not initialized");

	// Success and error responses may use different content types
	exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	exchange.getResponse().getHeaders().clearContentHeaders();

	InvocableHandlerMethod invocable =
			this.methodResolver.getExceptionHandlerMethod(exception, exchange, handlerMethod);

	if (invocable != null) {
		ArrayList<Throwable> exceptions = new ArrayList<>();
		……
		// Expose causes as provided arguments as well
		Throwable exToExpose = exception;
		while (exToExpose != null) {
			exceptions.add(exToExpose);
			Throwable cause = exToExpose.getCause();
			exToExpose = (cause != exToExpose ? cause : null);
		}
		@Nullable Object[] arguments = new Object[exceptions.size() + 1];
		exceptions.toArray(arguments);
		arguments[arguments.length - 1] = handlerMethod;

		return invocable.invoke(exchange, bindingContext, arguments)
				.onErrorResume(invocationEx ->
						handleExceptionHandlerFailure(exchange, exception, invocationEx, exceptions, invocable));
	}
	return Mono.error(exception);
}

关键点:

  1. 先清掉内容协商的"生产类型"属性与 content header——成功响应与错误响应可能用不同的 media type。
  2. methodResolver.getExceptionHandlerMethod(exception, exchange, handlerMethod):查找 @ExceptionHandler 方法。查找顺序(ControllerMethodResolver.getExceptionHandlerMethod,第 413-458 行):先在控制器类内部的 @ExceptionHandler 方法里匹配(exceptionHandlerCache),再到**@ControllerAdvice 全局类**里按适用性匹配(exceptionHandlerAdviceCache,装配于 initControllerAdviceCaches,第 262-294 行扫描所有 @ControllerAdvice bean)。
  3. 异常链展开为参数:while (exToExpose != null) 把异常及其所有 cause 依次收集进 exceptions 数组,最后一个元素是原 handlerMethod——这就是为什么 @ExceptionHandler 方法可以声明 @ExceptionHandler({A.class, B.class}) 或直接写 catch 形参 (Exception ex) 都能拿到正确类型的异常实参。
  4. 找到就调用:invocable.invoke(exchange, bindingContext, arguments)——@ExceptionHandler 方法本身也是个 InvocableHandlerMethod,返回值同样会被 resultHandler 写回(这就是 19.3.12 里 handleResultMono 把异常处理器返回值再走一遍 handleResult 的原因)。找不到匹配的 @ExceptionHandler → return Mono.error(exception) 继续向上抛,最终落到第一层 WebFluxResponseStatusExceptionHandler,再不行落到 HttpWebHandlerAdapter.handleUnresolvedError 写 500。

19.3.28 两个关键异常类型

  • ResponseStatusException(spring-web/src/main/java/org/springframework/web/server/ResponseStatusException.java):携带 HttpStatusCode + 可选 reason + headers 的异常基类。业务代码主动抛它(如 throw new ResponseStatusException(HttpStatus.NOT_FOUND)),框架第一层直接翻译状态码。
  • ServerWebInputException(同包):请求输入层面的异常基类——参数缺失、类型转换失败、body 解码失败等都以它或其子类(MissingRequestValueException、UnsupportedMediaTypeStatusException、NotAcceptableStatusException、MethodNotAllowedException 等)抛出,便于全局 @ExceptionHandler 按族捕获。

与 Servlet 模型的关键差异:Servlet 里 @ExceptionHandler 由 DispatcherServlet 通过 HandlerExceptionResolver 链处理,且发生在视图渲染之前;WebFlux 里异常处理是响应式链上的 onErrorResume 操作符,可以覆盖"选 handler 之前"到"响应渲染中"的全部阶段,且 @ExceptionHandler 的返回值自己也能被序列化成 JSON body——这就是 @RestControllerAdvice 全局错误响应的完整实现。

19.4 运行机制:一次 GET 请求的完整链路

19.4.1 时序总览

把前面剖析的所有类串起来,一次 GET /users/42 在 7.0.9 中的完整跳变如下(泳道:Netty 事件循环线程 → 各 WebFlux 组件,全程无请求专属线程):

Netty 事件循环线程 (event loop)
  │
  │ ① TcpServer 接收到字节流,解码出 HTTP 请求
  ▼
ReactorHttpHandlerAdapter.apply(reactorRequest, reactorResponse)          [spring-web http.server.reactive]
  │  new NettyDataBufferFactory(alloc)
  │  new ReactorServerHttpRequest / new ReactorServerHttpResponse
  ▼
HttpWebHandlerAdapter.handle(request, response)                          [spring-web web.server.adapter]
  │  forwardedHeaderTransformer.apply(request)(如配置)
  │  createExchange → new DefaultServerWebExchange(...)
  │  挂 ServerRequestObservationContext 到 exchange.attributes
  ▼
ExceptionHandlingWebHandler.handle(exchange)                             [spring-web web.server.handler]
  │  super.handle(exchange) = FilteringWebHandler.handle(exchange)
  ▼
FilteringWebHandler → DefaultWebFilterChain.filter(exchange)            │
  │  F1.filter(exchange, chain) —— 如认证过滤器                          │
  │     chain.filter(exchange) → F2.filter(...) →  …  → 链末节点         │
  ▼                                                                     │
DispatcherHandler.handle(exchange)                                       │  全链 onError 兜底
  │  Flux.concatMap(mapping -> mapping.getHandler(exchange))             │
  │    RouterFunctionMapping.getHandlerInternal(order=-1,未匹配,empty) │
  │    RequestMappingHandlerMapping.getHandlerInternal                    │
  │      → AbstractHandlerMapping.getHandler 包 CORS 检查                  │
  │      → 匹配到 GET /users/{id} → HandlerMethod(UserController#getUser)│
  │  .next() 取到 handler                                                 │
  ▼
handleRequestWith(exchange, handler)
  │  adapter.supports(handler) → RequestMappingHandlerAdapter(HandlerMethod 实例)
  ▼
RequestMappingHandlerAdapter.handle(exchange, handler)
  │  new InitBinderBindingContext(@InitBinder 列表)
  │  methodResolver.getRequestMappingMethod → InvocableHandlerMethod
  │  modelInitializer.initModel(...)(@ModelAttribute 方法)
  │  invocableMethod.invoke(exchange, bindingContext)
  │     ├─ getMethodArgumentValues:Mono.zip 聚合所有参数
  │     │    42 → PathVariableMethodArgumentResolver
  │     │    exchange → ServerWebExchangeMethodArgumentResolver
  │     └─ method.invoke(controller, args) → 返回 Mono<User>
  │  result.setExceptionHandler(exceptionHandler)
  ▼
handleResultMono(exchange, resultMono)
  │  flatMap(result -> handleResult(exchange, result, "Handler ..."))
  ▼
handleResult:遍历 resultHandlers
  │  ResponseEntityResultHandler.supports? 否(返回类型是 Mono<User>)
  │  ViewResolutionResultHandler.supports? 否
  │  ResponseBodyResultHandler.supports? 是(@RestController)
  ▼
ResponseBodyResultHandler.handleResult
  │  writeBody(body=Mono<User>, ...)
  │    adapter.toPublisher(body) → 订阅 Mono<User>
  │    selectMediaType → application/json
  │    Jackson2JsonEncoder.canWrite(User, json) → write(publisher)
  │      → 订阅 publisher,User 到达后编码为 DataBuffer
  ▼
ServerHttpResponse.writeWith(Flux<DataBuffer>)
  │  ReactorServerHttpResponse → 写回 Netty channel
  ▼
Netty 事件循环:字节冲刷给客户端
  │
  └─ 信号链回程:onComplete → HttpWebHandlerAdapter 后置链
        doOnSuccess(logResponse) → cleanupMultipart → setComplete()
        → ObservationSignalListener 停表(记录耗时/状态)

这张图里最值得反复体会的三点:

  1. 全程没有为这个请求新开线程。Netty 事件循环线程从 ① 一直跑到参数解析、甚至跑到控制器方法被调用(直到第一个异步边界出现)。控制器返回 Mono<User> 后,订阅该 Mono 的线程由产生数据的位置决定——数据库驱动的回调线程、或 Reactor 的调度线程。
  2. "调用"与"订阅"的对应关系:InvocableHandlerMethod.invoke 里反射调用控制器方法发生在哪个线程,取决于参数 Mono 链的最后一个信号在哪个线程发出——这是响应式编程"线程不固定"的根源(见 19.5.2 的坑)。
  3. 错误路径的位置:图上任何一步失败产生 error 信号,向上穿越 ResponseBodyResultHandler → DispatcherHandler.handleResultMono → RequestMappingHandlerAdapter.onErrorResume(@ExceptionHandler)→ ……,最终若无人接管则穿越回 ExceptionHandlingWebHandler 的 onErrorResume 链与 HttpWebHandlerAdapter.handleUnresolvedError。异常从不通过 Java 同步调用栈向上抛,只通过信号链。

19.4.2 线程模型:与 Servlet 模型的根本差异

Servlet 模型:每请求一线程,线程阻塞在 JDBC/REST 调用上,并发连接数 ≈ 线程数。WebFlux 模型:事件循环线程(Reactor Netty 默认 CPU 核数 个)只做非阻塞 IO 与信号传递;阻塞型业务代码必须显式挪开。7.0.9 为此提供的机制已在 19.3.14 出现:RequestMappingHandlerAdapter.setBlockingExecutor(executor) + setBlockingMethodPredicate(...),默认谓词 NonReactiveHandlerMethodPredicate 判定"返回类型不被 ReactiveAdapterRegistry 识别且非 Kotlin 挂起函数"的方法为阻塞型,用 resultMono.subscribeOn(optionalScheduler) 切到配置的专用调度器执行。

背压在这条链上的传递:请求体侧,ReactorServerHttpRequest.getBody() 返回的 Flux<DataBuffer> 由 Netty 按 channel 可读事件推,Jackson @RequestBody 解码器通过 Subscription.request(n) 控制拉取;响应体侧,writeWith(Flux<DataBuffer>) 内部(AbstractListenerWriteProcessor,spring-web 同包)监听 channel 可写水位回调向上游 request。应用层只要不做 collectList() 这类吞掉背压的聚合,内存占用就与流的速率挂钩,而不是与数据总量挂钩。

19.5 扩展用法与最佳实践

19.5.1 自定义 WebFilter:横切关注点的标准落点

实现 WebFilter 接口并注册为 bean 即可入链,无需改动 DispatcherHandler 任何代码(回顾 19.3.5:WebHttpHandlerBuilder.applicationContext 自动收集所有 WebFilter bean 并按 @Order 排序)。典型骨架:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TraceIdWebFilter implements WebFilter {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		String traceId = exchange.getRequest().getHeaders().getFirst("X-Trace-Id");
		if (traceId == null) {
			traceId = UUID.randomUUID().toString();
		}
		exchange.getResponse().getHeaders().add("X-Trace-Id", traceId);
		exchange.getAttributes().put("traceId", traceId);
		return chain.filter(exchange);   // 放行;不调用即短路
	}
}

最佳实践:

  1. 过滤器里不要做阻塞操作。过滤器运行在事件循环线程链路上,Thread.sleep/JDBC 直连会直接卡住事件循环——必须阻塞时用 Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic()) 包装。
  2. 短路语义要显式:鉴权失败时 chain.filter 不调用,直接 exchange.getResponse().setStatusCode(401).setComplete() 返回完成 Mono——这与 Servlet chain.doFilter 不调用等效,但返回类型必须是 Mono<Void>。
  3. 顺序靠 @Order:order 越小越先进入链(注意"先进入"= 装饰外层,其逻辑在放行前执行、在放行后收尾——类似 Servlet Filter 的 finally 语义)。

19.5.2 全局异常处理:@RestControllerAdvice + @ExceptionHandler

回顾 19.3.27:@ExceptionHandler 的查找顺序是"控制器本地方法 → 适用的 @ControllerAdvice 全局方法"。全局处理的标准写法:

@RestControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(ResponseStatusException.class)
	public Mono<ResponseEntity<ErrorBody>> handleStatus(ResponseStatusException ex) {
		return Mono.just(ResponseEntity.status(ex.getStatusCode())
				.body(new ErrorBody(ex.getReason())));
	}

	@ExceptionHandler(ServerWebInputException.class)
	public Mono<ResponseEntity<ErrorBody>> handleInput(ServerWebInputException ex) { …… }
}

要点(全部有源码依据):

  • @ExceptionHandler 方法可以返回 Mono<X>、ResponseEntity<X>、或直接 X——它的返回值与普通控制器方法一样,经过 handleResultMono 里第二轮 handleResult 走 resultHandler 写回(19.3.12)。
  • 形参声明异常类型即可被框架匹配(ExceptionHandlerMethodResolver 按异常类型继承关系找最佳匹配);也可以同时声明 ServerWebExchange exchange、HandlerMethod handlerMethod 等——这些走 exceptionHandlerResolvers 那套参数解析器(19.3.16 的裁剪版)。
  • 状态码优先级:异常先被框架层 WebFluxResponseStatusExceptionHandler 看到?不会——ExceptionHandlingWebHandler 是外层,但 RequestMappingHandlerAdapter 的 onErrorResume 在内层优先接住并尝试 @ExceptionHandler;只有 @ExceptionHandler 找不到时,error 才冒泡到外层 WebExceptionHandler。这就是"全局 advice 优先于框架默认 500"的源码原因。

19.5.3 WebClient 的链路追踪 header 传递

结合 19.3.26:写一个 ExchangeFilterFunction.ofRequestProcessor,从 Reactor Context 取上下文(推荐 Mono.deferContextual 或 Micrometer 的 ObservationThreadLocalAccessor 桥接),注入 traceparent 头;同时用 defaultHeader(...) 注入公共 header。注意 filter 内不要尝试从 ThreadLocal 读 traceId——请求处理线程随时在切换,ThreadLocal 大概率是空的(详见 19.6.3)。

19.5.4 函数式模型的组合扩展

函数式模型的扩展就是组合:RouterFunctions.route().pathPrefix("/api").nest(...) 或直接 .andOther(otherRouterFunction);局部加过滤器用 RouterFunction.filter(HandlerFilterFunction)——它与 WebFilter 等价但只作用于该路由。两种模型可以在同一应用共存(RouterFunctionMapping order=-1 先试),这在遗留系统迁移时很实用。

19.6 常见问题与坑(源码级解释)

19.6.1 block() 死锁与阻塞事件循环

现象:在控制器方法或 WebFilter 里对另一个 WebClient 调用 .block() 取值,轻则吞吐骤降,重则整个事件循环卡死、应用假死。

源码级解释:Reactor Netty 的请求处理订阅链跑在事件循环线程上。block() 的语义是"挂起当前线程等异步结果"——在事件循环线程上 block,等于把这个事件循环核冻死,该核上所有其他连接全部排队;若所有核都被 block 占满,整个服务停止接收新事件。更隐蔽的是"自调用死锁":某些调度器(如单线程 Scheduler)里 block 等同一个调度器上的任务完成,必然死锁。

正确姿势:全程返回 Mono/Flux 让框架订阅;必须对接旧式阻塞库时(JDBC、旧 HTTP 客户端),用 Mono.fromCallable(() -> oldDb.query()) .subscribeOn(Schedulers.boundedElastic()) 把阻塞动作挪到弹性线程池,而不是在控制器里 block。Spring 官方的 BlockHound(WebHttpHandlerBuilder 里还内置了 SpringWebBlockHoundIntegration,第 475-485 行)能在测试期直接探测事件循环上的阻塞调用。

19.6.2 共享可变状态与线程安全

现象:控制器里用成员变量累计计数,并发下数值错乱。

源码级解释:DispatcherHandler 是单例 bean,但它处理每个请求时不保证在固定线程(19.4.1:参数 Mono 链的最后信号来自哪个线程是不确定的)。控制器方法里访问的任何共享可变状态,都必须自身线程安全——private int count++ 这种写法在 Servlet 模型里就有风险(单例 controller 同样多线程),在 WebFlux 里只是更明显。正确做法:无状态 + 把请求相关状态放进 ServerWebExchange.getAttributes()(每请求独立),或用原子类型。

19.6.3 Reactor Context 与 ThreadLocal:事务/安全上下文失效

现象:在 Servlet 里好用的 SecurityContextHolder、TransactionSynchronizationManager,迁到 WebFlux 后取不到值;或"明明设置了,下游过滤器里读出来是 null"。

源码级解释:ThreadLocal 是线程绑定的,而响应式链的每一步可能在不同线程执行(19.4.1 已示)。Reactor 提供了替代机制:Context——通过 Mono.deferContextual(contextView -> ...) 或 contextWrite(...) 沿订阅链向上游传播(注意方向:context 是下游向上游写)。Spring 自己的安全上下文(Spring Security 6+ 的 ReactiveSecurityContextHolder)就是基于 Reactor Context 实现的;事务上下文(ReactiveTransactionManager)同理。源码旁证:HttpWebHandlerAdapter.ObservationSignalListener.addToContext(第 400-402 行)把 Observation 放进 Reactor Context,就是替代 ThreadLocal 的标准手法。结论:WebFlux 世界里,跨异步边界传递数据的唯一可靠通道是 Reactor Context 与 exchange.attributes,不是 ThreadLocal。

19.6.4 请求取消与异常传播

现象:客户端断开后,服务端日志打出一堆 "connection reset" 或 "pipe closed",业务流还在跑。

源码级解释:客户端断开 → Reactor Netty 取消订阅 → 响应式链收到 cancel 信号,各操作符据此停止发信号。源码旁证:HttpWebHandlerAdapter.ObservationSignalListener.doOnCancel(第 410-415 行)专门处理取消信号、把 observation 标记为 connectionAborted;handleUnresolvedError 里 disconnectedClientHelper.checkAndLogClientDisconnectedException(ex) 对断开类异常只静默处理。坑:如果业务流是纯同步循环(如手写 for 循环拼数据),cancel 信号无法抢占它——必须使用响应式操作符(Flux.generate/Flux.create)才能让取消及时生效。

19.6.5 背压处理不当导致的内存问题

现象:上传大文件或返回大结果集时 OOM。

源码级解释:ServerHttpRequest.getBody() 是 Flux<DataBuffer>,背压由下游 request 决定;但 body.collectList() 把整个流聚合成一个列表——下游 request(1) 一次性要全部元素,上游无限制地推,OOM 随之而来。@RequestBody 的解码器(AbstractMessageReaderArgumentResolver 体系)默认要求整个 body 聚合后解码,因此 @RequestBody Mono<LargeObject> 与直接收 Flux<DataBuffer> 做流式处理是两条不同的性能路线:前者全量入内存,后者受控背压。大文件场景应直接处理 Flux<DataBuffer>(如写文件)或用 DataBufferUtils 算子。

19.6.6 与 Servlet 过滤器/拦截器的语义差异

  • WebFilter vs Filter:WebFilter 是接口 + bean 自动收集(按 order 排序),没有 URL 模式配置;URL 级过滤要在过滤器内部判断 exchange.getRequest().getPath()。
  • WebFlux 没有 HandlerInterceptor:WebFlux 的"拦截"能力由 WebFilter(链外层)与 @ExceptionHandler(链内层)分担;Servlet HandlerInterceptor.preHandle/postHandle 的两个钩子在 WebFlux 里分别对应"WebFilter 放行前后"与"resultHandler 写回前后"(可在自定义 WebFilter 里 chain.filter(exchange).doFinally(...) 复刻 postHandle 语义)。
  • 异常模型:Servlet 里异常可以在 controller 内同步抛出、由 DispatcherServlet 统一捕获;WebFlux 里异常必须是 error 信号——在控制器方法里同步 throw 也会被 InvocableHandlerMethod 转成 Mono.error(19.3.15),但在异步回调(如 Flux.create 的 lambda)里直接 throw 不会进入框架链,必须通过 sink.error(ex) 传出。

19.3 源码剖析(九):补充深挖——ServerWebExchange 的懒加载与 @RequestBody 解码链

19.3.29 DefaultServerWebExchange:每请求上下文总管的字段设计

HttpWebHandlerAdapter.createExchange 一行 new DefaultServerWebExchange(...) 背后,是这个类(spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java)精心选择的字段集合(第 86-102 行):

private final ServerHttpRequest request;
private final ServerHttpResponse response;
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
private final Mono<WebSession> sessionMono;
private final LocaleContextResolver localeContextResolver;
private final Mono<MultiValueMap<String, String>> formDataMono;
private final Mono<MultiValueMap<String, Part>> multipartDataMono;
……
private final @Nullable ApplicationContext applicationContext;

这份字段表体现了响应式交换对象的两条设计原则:

  1. attributes 用 ConcurrentHashMap:exchange 在整条链上被多线程传递(过滤器在事件循环线程、控制器可能在回调线程、resultHandler 写回又回到事件循环),属性 map 必须线程安全。所有"按名存取"的请求级数据——observation context、最佳匹配模式、traceId——都走这里。
  2. sessionMono/formDataMono/multipartDataMono 全部是 Mono,而且是"懒加载 + 缓存"。以 getSession()(第 233 行附近)为例:不是构造时就去查 session 存储,而是构建一个委托给 WebSessionManager.getSession(exchange) 的 Mono,首次被订阅时才真正解析 session id(Cookie 或 header)、查/建会话,结果被缓存。form 表单与 multipart 同理:只有参数解析器真正调用 exchange.getFormData() 时,请求体才被消费成表单 map——这解释了为什么 WebFlux 里"form 表单读取"与"@RequestBody JSON 读取"是两条互斥的消费路径:请求体 Flux<DataBuffer> 只能被订阅一次,一旦 formDataMono 消费了 body,再走 @RequestBody 解码就会拿到空流。这也是 AbstractMessageReaderArgumentResolver.readBody 对 APPLICATION_FORM_URLENCODED 直接返回 415 错误(第 167-172 行)的原因——表单类型必须走 exchange.getFormData(),不能走消息阅读器。

19.3.30 @RequestBody 的解码链:readBody 逐段

RequestBodyMethodArgumentResolver 继承 AbstractMessageReaderArgumentResolver,后者的 readBody(spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java 第 140-235 行)是整个 body 解码的总装车间:

ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParam);
……
ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry().getAdapter(resolvedType) : null);
ResolvableType elementType = (adapter != null ? bodyType.getGeneric() : bodyType);
isBodyRequired = isBodyRequired || (adapter != null && !adapter.supportsEmpty());

先剥壳:参数声明 Mono<User> 时 adapter 非空,elementType 是 User;声明裸 User 时 elementType 就是 User 本身。isBodyRequired 还会被响应式类型特性自动收紧——Mono<User>(supportsEmpty=false)即使注解写 required=false 也按必选处理,因为空 body 根本无法产出 User。

随后读 Content-Type,解析失败抛 UnsupportedMediaTypeStatusException(415)。核心循环(第 180-215 行):

for (HttpMessageReader<?> reader : getMessageReaders()) {
	if (reader.canRead(elementType, mediaType)) {
		Map<String, Object> readHints = Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix());
		if (adapter != null && adapter.isMultiValue()) {
			Flux<?> flux = reader.read(actualType, elementType, request, response, readHints);
			flux = flux.onErrorMap(ex -> handleReadError(bodyParam, ex));
			if (isBodyRequired) {
				flux = flux.switchIfEmpty(Flux.error(() -> handleMissingBody(bodyParam)));
			}
			if (hints != null) {
				flux = flux.doOnNext(target ->
						validate(target, hints, bodyParam, bindingContext, exchange));
			}
			return Mono.just(adapter.fromPublisher(flux));
		}
		else {
			Mono<?> mono = reader.readMono(actualType, elementType, request, response, readHints);
			mono = mono.onErrorMap(ex -> handleReadError(bodyParam, ex));
			if (isBodyRequired) {
				mono = mono.switchIfEmpty(Mono.error(() -> handleMissingBody(bodyParam)));
			}
			if (hints != null) {
				mono = mono.doOnNext(target ->
						validate(target, hints, bodyParam, bindingContext, exchange));
			}
			return (adapter != null ? Mono.just(adapter.fromPublisher(mono)) : Mono.from(mono));
		}
	}
}

三条分支对应三种编程习惯:

  1. Flux<User> 或裸流式:reader.read(...) 返回 Flux,逐元素解码——每个 DataBuffer 到达就增量解析(如 NDJSON 行流),背压完整保留;
  2. Mono<User> / Single<User>:reader.readMono(...) 返回 Mono——Jackson 解码器会聚合整个 body 再一次性反序列化为对象,空 body 触发 switchIfEmpty(handleMissingBody)(required 时抛 400);
  3. 裸 User:同分支二,但最后 return Mono.from(mono)——把解码 Mono 作为参数值返回给 InvocableHandlerMethod.getMethodArgumentValues,与其他参数 Mono 一起被 Mono.zip 等待。

错误映射也在这段里完成(handleReadError,第 237-240 行片段):

private Throwable handleReadError(MethodParameter parameter, Throwable ex) {
	if (ex instanceof DataBufferLimitException) {
		return new ContentTooLargeException(ex);
	}
	……

DataBufferLimitException(聚合 body 超过 spring.codec.max-in-memory-size 限制)被翻译成 ContentTooLargeException(413)——这就是 19.6.5 提到的"内存上限"在源码里的落点:不是无限聚合,而是解码器聚合到上限就主动失败。

循环结束仍无 reader 匹配的兜底(第 219-234 行):POST/PUT/PATCH 且无 Content-Type 时,订阅 body 看是否真有字节——有字节说明客户端发了东西但服务端没认识的解码器,抛 415;没字节则按 required 判定缺失。这段"先看 body 是否为空再决定报 415 还是 400"的细节,是 WebFlux 对 REST 客户端友好性的体现。

19.3.31 与 Servlet DispatcherServlet 的对照小结

为避免读者把两套模型混淆,最后做一张精确对照表(均以 7.0.9 源码事实为准):

维度spring-webmvc(Servlet)spring-webflux(本章)
入口DispatcherServlet 实现 HttpServletHttpHandler + ReactorHttpHandlerAdapter
请求线程模型每请求一线程,阻塞到底事件循环 + 响应式回调,阻塞需显式挪调度器
分发器DispatcherServlet.doDispatchDispatcherHandler.handle
handler 查找HandlerMapping.getHandler(同步返回)HandlerMapping.getHandler 返回 Mono<Object>
参数解析HandlerMethodArgumentResolver(同步返回值)HandlerMethodArgumentResolver 返回 Mono<Object>,Mono.zip 聚合
异常处理HandlerExceptionResolver 链(同步)onErrorResume 信号链 + WebExceptionHandler
返回值写回HandlerMethodReturnValueHandlerHandlerResultHandler.handleResult 返回 Mono<Void>
过滤器Servlet Filter(按 URL 模式注册)WebFilter bean 自动收集(按 order)
sessionHttpSession(线程绑定)WebSession(Mono<WebSession>,懒加载缓存)

两套模型共享的部分是 spring-web 里的 @RequestMapping 注解定义、HandlerMethod(org.springframework.web.method 包)、ProblemDetail 等——所以控制器业务代码在两套模型间最大的差异就是返回类型从同步值变成 Mono/Flux。

19.3.32 WebSession:响应式会话的懒取与提交时保存

19.3.29 说 session 是懒加载的,这里补上它在源码层面的完整语义。HttpWebHandlerAdapter 构造时 this.sessionManager = new DefaultWebSessionManager()(未显式配置即默认)。DefaultWebSessionManager(spring-web/src/main/java/org/springframework/web/server/session/DefaultWebSessionManager.java 第 84-124 行)的核心是 getSession:

@Override
public Mono<WebSession> getSession(ServerWebExchange exchange) {
	return Mono.defer(() -> retrieveSession(exchange)
			.switchIfEmpty(createWebSession())
			.doOnNext(session -> exchange.getResponse().beforeCommit(() -> save(exchange, session))));
}

private Mono<WebSession> retrieveSession(ServerWebExchange exchange) {
	return Flux.fromIterable(getSessionIdResolver().resolveSessionIds(exchange))
			.concatMap(this.sessionStore::retrieveSession)
			.next();
}

三个设计要点:

  1. Mono.defer + resolveSessionIds:session id 从 WebSessionIdResolver 解析(接口在同包,只有三个方法:resolveSessionIds(exchange)/setSessionId(exchange, id)/expireSession(exchange));默认实现 CookieWebSessionIdResolver 从 Cookie 读,HeaderWebSessionIdResolver 从 header 读。retrieveSession 用 concatMap 逐个候选 id 去 WebSessionStore(默认 InMemoryWebSessionStore)查,取第一个非空。
  2. 查不到就建:switchIfEmpty(createWebSession())——首次访问自动建会话,且不在此处立刻发 Cookie。
  3. 保存动作挂在响应提交前:beforeCommit(() -> save(exchange, session))——这正是 19.3.2 节 ReactiveHttpOutputMessage.beforeCommit 的用武之地。save(第 105-124 行)逻辑:会话已开始且未过期时,若请求里没有旧 id 或 id 变了,sessionIdResolver.setSessionId 把新 id 写进响应 Cookie;最后 session.save() 由 store 持久化。若会话已过期/失效,则 expireSession 让客户端删除 Cookie。

这套设计与 Servlet HttpSession 的本质差别:Servlet 容器在请求进入时就创建/恢复 session 并绑定线程;WebFlux 的 session 是一个普通 Mono,只有业务代码调用 exchange.getSession() 时才触发解析与建库动作,且写回响应的动作被推迟到响应提交前——少一次不必要的会话 IO,也避免了"没人用却每个请求都查一次"的开销。

19.7 本章小结

本章按"自底向上"的顺序剖析了 Spring Framework 7.0.9 中 WebFlux 的完整架构:

  1. L0/L1 边界:HttpHandler 是最底层契约,ReactorHttpHandlerAdapter 把它与 Reactor Netty 桥接;ServerHttpRequest/ServerHttpResponse 以 Flux<DataBuffer>/writeWith(Publisher) 把请求体与响应体建模为背压感知的数据流。
  2. 装配链:WebHttpHandlerBuilder.build() 用三行 new 织出 HttpWebHandlerAdapter → ExceptionHandlingWebHandler → FilteringWebHandler → 目标 WebHandler(DispatcherHandler) 的装饰结构;DefaultWebFilterChain 用不可变链表实现过滤器链,启动期构建一次、全请求共享。
  3. 分发核心:DispatcherHandler.handle 以 Flux.concatMap(getHandler).next() 串行匹配 HandlerMapping,再走 HandlerAdapter.handle → HandlerResult → HandlerResultHandler.handleResult 两跳;错误以 onErrorResume 信号形式在链路各层被消化。
  4. 注解模型:RequestMappingHandlerMapping 扫描期建立 PathPattern 映射表(7.0 新增 CGLIB 可见性校验);InvocableHandlerMethod.getMethodArgumentValues 用 Mono.zip 并行解析全部参数;ControllerMethodResolver.initResolvers 维护了 27 个内置参数解析器(注解型→类型型→自定义→兜底型的优先级顺序);返回值经 ResponseBodyResultHandler → AbstractMessageWriterResultHandler.writeBody → HttpMessageWriter 落到 ServerHttpResponse.writeWith。
  5. 函数式模型:RouterFunction.route(request) 异步匹配、HandlerFunctionAdapter 调 HandlerFunction.handle、ServerResponseResultHandler 委托 ServerResponse 自我渲染;与注解模型共用 DispatcherHandler,但 Mapping order=-1 优先。
  6. 客户端:WebClient 是 ExchangeFunction + 折叠后的 ExchangeFilterFunction 的惰性门面;retrieve 声明式处理状态码、exchange 原生式交还 ClientResponse。
  7. 错误处理两层结构:框架层 WebFluxResponseStatusExceptionHandler(ResponseStatusException/@ResponseStatus 翻译)兜底应用层 @ExceptionHandler(RequestMappingHandlerAdapter 的 DispatchExceptionHandler 实现),最终兜底在 HttpWebHandlerAdapter.handleUnresolvedError。

本章关键类清单

类名模块与相对路径职责一句话
HttpHandlerspring-web/src/main/java/org/springframework/http/server/reactive/HttpHandler.java最底层响应式 HTTP 契约:Mono<Void> handle(ServerHttpRequest, ServerHttpResponse)
ServerHttpRequestspring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java请求抽象:方法/URI/路径/查询参数 + getBody() 的 Flux<DataBuffer>
ServerHttpResponsespring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java响应抽象:状态码 + writeWith(Publisher)/setComplete()
ReactorHttpHandlerAdapterspring-web/src/main/java/org/springframework/http/server/reactive/ReactorHttpHandlerAdapter.javaReactor Netty 到 Spring HttpHandler 的唯一桥接(含 HEAD 装饰、非法 URI→400)
WebHandler / WebFilter / WebExceptionHandlerspring-web/src/main/java/org/springframework/web/server/ 下同名接口L2 处理链三抽象,均以 ServerWebExchange 为中心
WebHttpHandlerBuilderspring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java装配链建造者:收集 WebFilter/WebExceptionHandler,build() 织出装饰链
HttpWebHandlerAdapterspring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java创建 ServerWebExchange、observation 挂载、全链最后的 500 兜底
ExceptionHandlingWebHandlerspring-web/src/main/java/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java把 WebExceptionHandler 列表织成 onErrorResume 链(首环为 CheckpointInsertingHandler)
FilteringWebHandler / DefaultWebFilterChainspring-web/src/main/java/org/springframework/web/server/handler/ 下不可变过滤器链,启动期构建一次并发复用
ResponseStatusExceptionHandler / WebFluxResponseStatusExceptionHandlerspring-web/.../handler/ResponseStatusExceptionHandler.java;spring-webflux/.../handler/WebFluxResponseStatusExceptionHandler.java框架层默认异常处理器:翻译 ResponseStatusException/@ResponseStatus 为状态码
DispatcherHandlerspring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java前端控制器:Mapping→Adapter→ResultHandler 三跳分发
HandlerMapping / HandlerAdapter / HandlerResult / HandlerResultHandlerspring-webflux/src/main/java/org/springframework/web/reactive/ 下分发三策略接口 + 调用结果载体
AbstractHandlerMappingspring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.javaMapping 公共骨架:API 版本解析、CORS 配置组合与拒绝
RequestMappingHandlerMappingspring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java注解扫描:isHandler(@Controller)、@RequestMapping→RequestMappingInfo、7.0 CGLIB 可见性校验
RequestMappingHandlerAdapterspring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java注解调用器:建绑定上下文、initModel、invoke、@ExceptionHandler 解析、阻塞调度
InvocableHandlerMethodspring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java参数 Mono.zip 聚合 + 反射调用 + 响应自处理判定
ControllerMethodResolverspring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java27 个内置参数解析器清单 + @ControllerAdvice 缓存
ResponseBodyResultHandler / AbstractMessageWriterResultHandlerspring-webflux/.../result/method/annotation/ 下@ResponseBody 写回:内容协商→HttpMessageWriter→writeWith
ResponseEntityResultHandlerspring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.javaResponseEntity 类型返回值(状态码/header/body 三段)写回
ViewResolutionResultHandlerspring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java视图名/View/Rendering 返回值的模板渲染
RouterFunctions / RouterFunction / HandlerFunctionspring-webflux/src/main/java/org/springframework/web/reactive/function/server/ 下函数式模型三原语:route/nest/builder
RouterFunctionMappingspring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java函数式模型的 HandlerMapping(order=-1 优先)
HandlerFunctionAdapterspring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/HandlerFunctionAdapter.java函数式模型的 HandlerAdapter
ServerResponseResultHandlerspring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerResponseResultHandler.java函数式返回值自我渲染入口
WebClient / DefaultWebClientspring-webflux/src/main/java/org/springframework/web/reactive/function/client/ 下响应式 HTTP 客户端门面与默认实现
ExchangeFilterFunctionspring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunction.java客户端请求/响应双向过滤器(ofRequestProcessor/ofResponseProcessor)
ResponseStatusException / ServerWebInputExceptionspring-web/src/main/java/org/springframework/web/server/ 下两大异常基类:状态码携带型 / 请求输入错误型
WebFluxConfigurationSupportspring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java@EnableWebFlux 默认装配:全部策略 bean 与其 order