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獲得。