1 概覽
在本教程中,我們將透過一個實際示例瞭解Spring WebFlux專案中處理錯誤的各種策略。
我們還將指出使用一種策略比另一種策略更有利的地方,並在最後提供完整原始碼的連結。
2 開始示例程式碼
maven 設定和之前介紹 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 中包含或返回其(sayHello方法的返回)結果的方法。
public Mono<ServerResponse> handleRequest(ServerRequest request) {
return
//...
sayHello(request)
//...
}
最後,實現 sayHello 方法,實現很簡單,直接拼接 hello 和引數 username 即可。
private Mono<String> sayHello(ServerRequest request) {
try {
// 應該是 username
return Mono.just("Hello, " + request.queryParam("username").get());
} catch (Exception e) {
return Mono.error(e);
}
}
因此,只要我們的請求中帶了 username 引數,我們的請求就能正常返回。舉個例子:我們請求“/hello?username=Tonni”,類似請求,我們總是能正常返回。
然而,如果我們的請求不帶 username 引數,我們的請求就會丟擲異常了。下面,我們來看看 Spring 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)
.bodyValue(s));
}
這裡,每當有問題的連線函式丟擲異常的時候,我們直接返回一個靜態結果:“Hello Stranger”。
3.2 onErrorResume 處理異常
有三種使用 onErrorResume 處理異常的方式:
- 計算動態回撥值
- 透過回撥函式執行其他分支
- 捕獲、包裝並重新丟擲錯誤,例如,作為自定義業務異常
讓我們看看怎麼計算值:
public Mono<ServerResponse> handleRequest(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)));
}
這裡,每當 sayHello 丟擲異常的時候,我們返回一個 “Error + 異常資訊(e.getMessage())”。
接下來,我們看看當異常發生呼叫回撥函式:
public Mono<ServerResponse> handleRequest(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)));
}
這裡,每當 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 丟擲異常的時候,我們丟擲一個定製異常 NameRequiredException,message是 “username is required”。
全域性處理異常
目前為止,我們提供的所有示例都在方法級別上處理了錯誤處理。但是我們可以選擇在全域性層面處理異常。為此,我們只需要兩步:
- 自定義一個全域性錯誤響應屬性
- 實現全域性錯誤處理 handler
這樣我們程式丟擲的異常將會自動轉換成 HTTP 狀態和 JSON 錯誤體。我們只需要繼承 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;
}
}
這裡,當異常丟擲是,我們想要返回 BAD_REQUEST 狀態碼和“username is required”錯誤資訊作為錯誤屬性的一部分。
然後,我們來實現全域性錯誤處理 handler。
為此,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,
ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
在這個例子中,我們設定 handler 的 order 為 -2。這是為了給它一個比預設 handler,也就是 DefaultErrorWebExceptionHandler 一個更高的優先順序,它設定的 order 為 -1。
errorAttributes 物件將是我們在 Web 異常處理程式的建構函式中傳遞的物件的精確副本。理想情況下,這應該是我們自定義的錯誤屬性類。
然後我們明確生命了,我們希望將所有的異常處理路由到 renderErrorResponse() 中。
最後,我們獲取了錯誤屬性並插入到服務端響應體中。
然後這會生成一個 JSON 響應,其中包含了錯誤的詳細資訊,HTTP 狀態、機器端的異常資訊等。對於瀏覽器端,它有一個 “white-label”錯誤處理程式,可以以 HTML 形式呈現相同的資料,當然這個頁面可以定製。
總結
在本文中,我們研究了在 Spring WebFlux 專案中處理異常的集中策略,並指出使用一個策略優於其他策略的地方。