在本教程中,我们将看一下处理Spring WebFlux项目中错误的各种策略,同时介绍一个实际案例。
我们还将指出在一个策略中使用另一个策略并在最后提供完整源代码的地址。
对于我们的示例来说,我们将使用RESTful请求,该请求将用户名作为查询参数,并返回“Hello username”作为结果。
首先,让我们创建一个/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”字符串和用户名的方法:
private Mono<String> sayHello(ServerRequest request) {
//...
return Mono.just("Hello, " + request.queryParam("name").get());
//...
}
只要使用用户名作为请求参数,例如,访问/hello?username=Tonni,则此请求将始终正常运行。
但是,如果我们在没有指定用户名的情况下调用相同的请求地址,例如/hello,它将抛出异常。
下面,我们将了解在何处以及如何重新组织我们的代码来处理WebFlux中的异常。
在功能级别上处理错误
Mono和Flux APIs内置了两个关键操作符,用于处理功能级别上的错误。
让我们简要地探讨它们及其用法。
每当发生错误时,我们可以使用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”结果。
我们可以使用三种方法在onErrorResume上处理错误:
- 计算动态fallback值
- 使用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()抛出异常时,我们就会调用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的自定义异常。
在全局级别上处理错误
到目前为止,我们提供的所有示例都在功能级别上处理了错误处理。
但是,我们可以选择在全局范围内处理我们的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对象将是我们在网络异常处理程序的构造函数传递一个的精确副本。理想情况下,这应该是我们自定义的Error Attributes类。
然后,我们清楚地表明我们想要将所有错误处理请求路由到renderErrorResponse()方法。
最后,我们获取错误属性并将它们插入服务器响应主体中。
然后,它会生成一个JSON响应,其中包含错误,HTTP状态和计算机客户端异常消息的详细信息。对于浏览器客户端,它有一个whitelabel错误处理程序,它以HTML格式呈现相同的数据。当然,这可以是定制的。
在案例中,我们研究了处理Spring WebFlux项目中错误的各种策略,并指出了各种策略的优势。