1. 概述
本文将深入探讨在 Spring WebFlux 项目中处理错误的多种策略,并通过一个实际案例进行演示。
我们会分析不同场景下哪种错误处理方式更合适,帮助你在实际开发中做出合理选择。文末附有完整的源码链接,方便查阅。
2. 示例准备
Maven 依赖配置与我们之前介绍 Spring WebFlux 入门 的文章一致,这里不再赘述。
本次示例将构建一个 RESTful 接口,接收 username
查询参数,并返回 "Hello username"
字符串。
首先,定义一个路由函数,将 /hello
的 GET 请求映射到处理器的 handleRequest
方法:
@Bean
public RouterFunction<ServerResponse> routes(Handler handler) {
return RouterFunctions.route(RequestPredicates.GET("/hello")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
handler::handleRequest);
}
接着,实现 handleRequest
方法,调用 sayHello()
并将其结果封装进 ServerResponse
:
public Mono<ServerResponse> handleWithGlobalErrorHandler(ServerRequest request) {
return
//...
sayHello(request)
//...
}
最后是 sayHello()
方法,它负责拼接字符串:
private Mono<String> sayHello(ServerRequest request) {
try {
return Mono.just("Hello, " + request.queryParam("name").get());
} catch (Exception e) {
return Mono.error(e);
}
}
只要请求中包含 username
参数(例如 /hello?name=Tonni
),接口就能正常工作。
⚠️ 但如果请求不带参数(如 /hello
),就会抛出 NoSuchElementException
。
接下来,我们就来看看如何在 WebFlux 中优雅地处理这类异常。
3. 函数式层级的错误处理
Mono
和 Flux
提供了两个核心操作符用于函数式错误处理,它们足够灵活,适用于基于注解和函数式路由(RouterFunction)两种编程模型。
3.1 使用 onErrorReturn
返回默认值
✅ 适用场景:出错时返回一个静态默认值,简单粗暴。
public Mono<ServerResponse> handleWithErrorReturn(ServerRequest request) {
return sayHello(request)
.onErrorReturn("Hello Stranger")
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s));
}
当 sayHello()
抛异常时,直接返回 "Hello Stranger"
。适合对容错要求不高、可降级的场景。
3.2 使用 onErrorResume
实现更复杂恢复逻辑
onErrorResume
更强大,支持三种典型用法:
✅ 动态生成 fallback 值
public Mono<ServerResponse> handleWithErrorResumeAndDynamicFallback(ServerRequest request) {
return sayHello(request)
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s))
.onErrorResume(e -> Mono.just("Error " + e.getMessage())
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s)));
}
返回包含具体错误信息的响应,便于调试,但注意不要把敏感信息暴露给前端。
✅ 调用备用方法(Fallback Method)
public Mono<ServerResponse> handleWithErrorResumeAndFallbackMethod(ServerRequest request) {
return sayHello(request)
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s))
.onErrorResume(e -> sayHelloFallback()
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s)));
}
出错时切换到备用逻辑,比如访问本地缓存或默认用户。这是典型的“服务降级”思路。
✅ 捕获、包装并重新抛出异常
public Mono<ServerResponse> handleWithErrorResumeAndCustomException(ServerRequest request) {
return ServerResponse.ok()
.body(sayHello(request)
.onErrorResume(e -> Mono.error(new NameRequiredException(
HttpStatus.BAD_REQUEST,
"username is required", e))), String.class);
}
将底层异常包装为业务异常(如 NameRequiredException
),便于后续统一处理。这是推荐做法,尤其在复杂业务流中。
⚠️ 注意:这里只是抛出异常,真正的处理仍需全局异常机制配合,见下文。
4. 全局错误处理
前面的函数式处理适合局部、特定逻辑的容错。但在实际项目中,我们更需要一个统一的全局异常处理机制,避免到处写 onErrorResume
。
Spring WebFlux 提供了 AbstractErrorWebExceptionHandler
,让我们可以自定义全局错误响应。
实现步骤如下:
步骤一:自定义错误属性(Error Attributes)
继承 DefaultErrorAttributes
,重写 getErrorAttributes()
方法,控制返回给客户端的错误信息:
public class GlobalErrorAttributes extends DefaultErrorAttributes{
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request,
ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(
request, options);
map.put("status", HttpStatus.BAD_REQUEST);
map.put("message", "username is required");
return map;
}
}
这样,当异常发生时,响应体中会包含我们指定的 status
和 message
字段。
步骤二:实现全局异常处理器
创建一个组件继承 AbstractErrorWebExceptionHandler
:
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
AbstractErrorWebExceptionHandler {
// constructors
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return RouterFunctions.route(
RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(
ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request,
ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
关键点说明:
@Order(-2)
:确保优先级高于 Spring Boot 默认的DefaultErrorWebExceptionHandler
(默认@Order(-1)
)。getRoutingFunction()
:将所有错误请求路由到renderErrorResponse
方法。renderErrorResponse()
:构造包含错误信息的 JSON 响应,状态码为 400。
✅ 最终效果:
- 对 API 客户端返回结构化 JSON 错误信息
- 对浏览器返回“白页”错误页面(可进一步定制)
5. 总结
本文介绍了 Spring WebFlux 中两类错误处理方式:
策略 | 适用场景 | 推荐度 |
---|---|---|
onErrorReturn |
简单降级,返回默认值 | ⭐⭐ |
onErrorResume |
动态 fallback、调用备用逻辑、包装异常 | ⭐⭐⭐⭐ |
全局异常处理器 | 统一错误格式、避免重复代码 | ⭐⭐⭐⭐⭐ |
✅ 最佳实践建议:
- 局部错误用
onErrorResume
包装为业务异常 - 全局统一通过
AbstractErrorWebExceptionHandler
处理并返回标准格式 - 避免在
onErrorReturn
中暴露敏感信息
💡 源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-reactive-modules/spring-reactive