1. 概览
在本教程中,我们通过一个实际的例子来看一下可用于处理Spring WebFlux项目中的错误的各种策略。
我们还将指出在哪种情况下使用一种策略会比另外一种好,在本文最后将提供所有源码的下载地址。
2. 配置实例
上一篇文章 previous article 中已经提到了maven的配置, 并对 Spring Webflux做了简单的介绍。
在这个例子中,我们为一个 RESTful 端点加上一个名为 username 的查询参数,并以“Hello username”作为结果返回。
First, let’s create a router function that routes the /hello request to a method named handleRequest in the passed-in handler:
首先,让我们创建一个路由器函数,将/hello请求路由名为handleRequest的方法中:
@Bean
public RouterFunction<ServerResponse> routeRequest(Handler handler) {
return RouterFunctions.route(RequestPredicates.GET("/hello")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
handler::handleRequest);
}
接下来,我们将定义handleRequest()方法,该方法调用sayHello()方法并在ServerResponse主体中包含/返回其结果的方法:
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return
//...
sayHello(request)
//...
}
最后,sayHello()是一个简单的实用工具方法,它将“Hello”和 username 连接起来返回。
private Mono<String> sayHello(ServerRequest request) {
//...
return Mono.just("Hello, " + request.queryParam("name").get());
//...
}
只要用 username 作为我们请求的一部分存在,例如使用“/hello?username=Tonni”访问,我们的端点就可以正确运行。
然而,如果我们调用"/hello"的时候没有使用 username 这个参数,它会抛出一个异常。
下面,我们将看看我们在何处如何重新组织我们的代码才能在WebFlux中处理此异常。
3. 在函数级别处理错误
Mono和Flux API内置了两个关键操作符,用于处理功能级别的错误。
让我们简要地探讨它们及其用法。
3.1. 使用 onErrorReturn
当出现错误时,我们可以使用 onErrorReturn()来返回一个静态的默认值。
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return sayHello(request)
.onErrorReturn("Hello Stranger")
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(s));
}
当 sayHello()抛出异常时,函数就会默认返回"Hello Stranger"。
3.2. 使用onErrorResume
使用onErrorResume处理错误有三种方式:
- 计算动态返回值
- 使用fallback方法 跳转到备份路径
- 捕获,包装和重新抛出错误,例如 作为自定义业务异常
让我们看看怎么杨计算一个值:
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return sayHello(request)
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(s))
.onErrorResume(e -> Mono.just("Error " + e.getMessage())
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(s)));
}
在这里,每当sayHello()抛出异常时,我们将返回一个字符串,该字符串由附加到字符串“Error”的动态获取的错误消息组成。
接下来,当错误发生时我们调用 fallback 方法:
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return sayHello(request)
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(s))
.onErrorResume(e -> sayHelloFallback()
.flatMap(s ->; ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.syncBody(s)));
}
在这里,只要sayHello()抛出异常,我们就会调用替代方法sayHelloFallback()。
使用onErrorResume()的最后一个选项是捕获,包装和重新抛出错误,例如 作为NameRequiredException:
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return ServerResponse.ok()
.body(sayHello(request)
.onErrorResume(e -> Mono.error(new NameRequiredException(
HttpStatus.BAD_REQUEST,
"username is required", e))), String.class);
}
在这里,只要sayHello()抛出异常,我们就会抛出一个自定义异常,并带有消息:"username is required"。
4. 全局级别的错误处理
到目前为止,我们提供的所有示例都在函数级别上处理了错误处理。
但是,我们可以选择在全局范围内处理我们的WebFlux错误。 要做到这一点,我们只需要采取两个步骤:
- 自定义全局错误响应属性
- 实现全局错误处理程序
我们的处理程序抛出的异常将被自动转换为HTTP状态和JSON错误正文。 要自定义这些,我们可以简单地扩展DefaultErrorAttributes类并覆盖其getErrorAttributes()方法:
public class GlobalErrorAttributes extends DefaultErrorAttributes{
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request,
boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(
request, includeStackTrace);
map.put("status", HttpStatus.BAD_REQUEST);
map.put("message", "username is required");
return map;
}
}
在这里,我们希望状态:BAD_REQUEST和消息:"username is required"在发生异常时作为错误属性的一部分返回。
接下来,让我们实现全局错误处理程序。 为此,Spring提供了一个方便的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, false);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorPropertiesMap));
}
}
在这个例子中,我们将全局错误处理程序的顺序设置为-2。 这是为了给它一个比在@Order(-1)注册的DefaultErrorWebExceptionHandler更高的优先级。
errorAttributes对象将是我们在Web异常处理程序的构造函数中传递的副本的精确副本。 理想情况下,这应该是我们自定义的Error Attributes类。
然后,我们清楚地说明我们想要将所有错误处理请求路由到renderErrorResponse()方法。
最后,我们获取错误属性并将它们插入服务器响应主体中。
然后,它会生成一个JSON响应,其中包含错误,HTTP状态和计算机客户端的异常消息的详细信息。 对于浏览器客户端,它有一个“whitelabel”错误处理程序,它以HTML格式呈现相同的数据。 当然,这可以是定制的。
5. 结尾
在本文中,我们研究了可用于处理Spring WebFlux项目中的错误的各种策略,并指出了使用一种策略而不是另一种策略的优势。
正如所承诺的那样,本文附带的完整源代码可以在 GitHub获得。