1. 概述
在本教程中,我們將探討在 Spring WebFlux 項目中處理錯誤的各種策略,同時會通過一個實際示例進行講解。
我們還會指出在某些情況下使用一種策略優於另一種策略,並在末尾提供指向完整源代碼的鏈接。
2. 設置示例
Maven 的設置與我們上一篇文章相同,它為 Spring WebFlux 提供了一個介紹。
對於我們的示例,我們將使用一個 RESTful 端點,它接受用户名作為查詢參數,並返回 “Hello username”作為結果。
首先,讓我們創建一個路由器函數,該函數將 /hello 請求路由到傳入的處理器的 handleRequest 方法:
@Bean
public RouterFunction<ServerResponse> routes(Handler handler) {
return RouterFunctions.route(RequestPredicates.GET("/hello")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
handler::handleRequest);
}
接下來,我們將定義 handleWithGlobalErrorHandler() 方法,該方法調用 sayHello() 方法,並找到將其結果包含在 ServerResponse 響應體中的方法:
public Mono<ServerResponse> handleWithGlobalErrorHandler(ServerRequest request) {
return
//...
sayHello(request)
//...
}
最後,sayHello() 方法是一個簡單的實用方法,它將“Hello” 和用户名連接起來:
private Mono<String> sayHello(ServerRequest request) {
try {
return Mono.just("Hello, " + request.queryParam("name").get());
} catch (Exception e) {
return Mono.error(e);
}
}
只要請求中包含用户名,例如如果端點被調用為“/hello?username=Tonni”,該端點將始終正常工作。
但是,如果調用相同的端點且未指定用户名,例如“/hello”,則會拋出異常。
下面,我們將探討如何在 WebFlux 中重新組織代碼以處理此異常。
3. 以函數級方式處理錯誤
Mono 和 Flux API 內置了兩個關鍵運算符,用於以函數級方式處理錯誤。
下面我們簡要地瞭解一下它們及其用法。
3.1. 處理錯誤使用 onErrorReturn
我們可以使用 onErrorReturn() 在發生錯誤時返回一個靜態默認值:
public Mono<ServerResponse> handleWithErrorReturn(ServerRequest request) {
return sayHello(request)
.onErrorReturn("Hello Stranger")
.flatMap(s -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(s));
}
我們在這裏返回一個靜態的“Hello Stranger”,當有問題的拼接函數 sayHello() 拋出異常時。
3.2. 使用 onErrorResume 處理錯誤
有三種方法可以使用 onErrorResume 處理錯誤:
- 計算動態備用值
- 使用備用方法執行替代路徑
- 捕獲、包裝和重新拋出錯誤,例如作為自定義業務異常
讓我們看看如何計算一個值:
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)));
}
我們返回一個字符串,該字符串包含動態獲取的錯誤消息,並將其與字符串“Error”連接,當 sayHello() 拋出異常時生效。
接下來,讓我們 在發生錯誤時調用備用方法:
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)));
}
我們在這裏在 sayHelloFallback() 方法被調用時,當 sayHello() 拋出異常時。
最終選項是使用 onErrorResume(),即 捕獲、包裝和重新拋出錯誤,例如作為 NameRequiredException:
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);
}
我們會在 sayHello() 拋出異常時,自定義拋出一個異常,異常信息為“username is required”。
4. 全局層面的錯誤處理
到目前為止,我們展示的所有示例都集中在功能級的錯誤處理上。需要注意的是,功能級的錯誤處理適用於基於標註的配置以及我們在本文中使用的 RouterFunction。
當然,我們可以使用標註來處理我們的 REST API 中的異常,但對於本文,我們將選擇使用全局錯誤處理程序來處理 WebFlux 中的錯誤。要做到這一點,我們只需要採取兩個步驟:
- 自定義全局錯誤響應屬性
- 實現全局錯誤處理程序
我們的處理程序拋出的異常將自動轉換為 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”的消息作為錯誤屬性的一部分。
接下來,讓我們 實現全局錯誤處理程序。
為此,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));
}
}
在此示例中,我們設置了全局錯誤處理器的優先級為-2。 這是為了 使其優先級高於 默認 Web 異常處理程序,該程序已註冊在 @Order(-1)
errorAttributes 對象將與我們在 Web 異常處理程序的構造函數中傳遞的完全相同。 理想情況下,它應該是我們自定義的錯誤屬性類。
然後我們明確説明,我們希望將所有錯誤處理請求路由到 renderErrorResponse() 方法。
最後,我們獲取錯誤屬性並將它們插入到服務器響應主體中。
這會產生一個包含錯誤詳細信息、HTTP 狀態和異常消息的 JSON 響應,用於機器客户端。 對於瀏覽器客户端,它具有“白標”錯誤處理程序,以 HTML 格式渲染相同的數據。 當然,這可以自定義。
5. 結論
在本文中,我們探討了處理 Spring WebFlux 項目中錯誤的各種策略,並指出了在某些情況下使用一種策略優於另一種策略的優勢。