本文內(nèi)容
- 為什么要全局異常處理?
- WebFlux REST 全局異常處理實(shí)戰(zhàn)
- 小結(jié)
摘錄:只有不斷培養(yǎng)好習(xí)慣路操,同時(shí)不斷打破壞習(xí)慣,我們的行為舉止才能夠自始至終都是正確的。
一仅偎、為什么要全局異常處理?
前后端分離開發(fā)雳殊,一般提供 REST API橘沥,正常返回會(huì)有響應(yīng)體,異常情況下會(huì)有對(duì)應(yīng)的錯(cuò)誤碼響應(yīng)夯秃。
挺多人咨詢的座咆,Spring Boot MVC 異常處理用切面 @RestControllerAdvice
注解去實(shí)現(xiàn)去全局異常處理。那 WebFlux 如何處理異常仓洼?如何實(shí)現(xiàn)統(tǒng)一錯(cuò)誤碼異常處理介陶?
全局異常處理的好處:
- 異常錯(cuò)誤碼等統(tǒng)一維護(hù)
- 避免一些重復(fù)代碼
二、WebFlux REST 全局異常處理實(shí)戰(zhàn)
下面介紹如何統(tǒng)一攔截異常色建,進(jìn)行響應(yīng)處理哺呜。
2.1 工程信息
- 運(yùn)行環(huán)境:JDK 7 或 8,Maven 3.0+
- 技術(shù)棧:SpringBoot 2.1.3
- 代碼地址:https://github.com/JeffLi1993/springboot-learning-example
- 模塊工程名: 2-x-spring-boot-webflux-handling-errors
工程結(jié)構(gòu):
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── Application.java
│ ├── error
│ │ ├── GlobalErrorAttributes.java
│ │ ├── GlobalErrorWebExceptionHandler.java
│ │ └── GlobalException.java
│ ├── handler
│ │ └── CityHandler.java
│ └── router
│ └── CityRouter.java
└── resources
└── application.properties
application.properties 無須配置镀岛,默認(rèn)即可
Application Spring Boot 應(yīng)用啟動(dòng)類弦牡,是可以用來啟動(dòng) Spring Boot 應(yīng)用。其包含了 @SpringBootApplication 注解和 SpringApplication 類漂羊,并調(diào)用 SpringApplication 類的 run() 方法驾锰,就可以啟動(dòng)該應(yīng)用。
具體實(shí)現(xiàn)類的關(guān)系圖如下:
圖片上傳失敗走越,見原文 https://www.bysocket.com/archives/2272
2.2 CityRouter 路由器類
城市路由器代碼如下:
@Configuration
public class CityRouter {
@Bean
public RouterFunction<ServerResponse> routeCity(CityHandler cityHandler) {
return RouterFunctions.route(RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), cityHandler::helloCity);
}
}
RouterFunctions 對(duì)請(qǐng)求路由處理類椭豫,即將請(qǐng)求路由到處理器,這將一個(gè) GET 請(qǐng)求 /hello 路由到處理器 cityHandler 的 helloCity 方法上。跟 Spring MVC 模式下的 HandleMapping 類似赏酥。
RouterFunctions.route(RequestPredicate, HandlerFunction) 方法喳整,對(duì)應(yīng)的 參是請(qǐng)求參數(shù)和處理函數(shù),如果請(qǐng)求匹配裸扶,就調(diào) 對(duì)應(yīng)的處理器函數(shù)框都。
2.3 CityHandler 服務(wù)處理類
城市服務(wù)器處理類,代碼如下:
@Component
public class CityHandler {
public Mono<ServerResponse> helloCity(ServerRequest request) {
return ServerResponse.ok().body(sayHelloCity(request), String.class);
}
private Mono<String> sayHelloCity(ServerRequest request) {
Optional<String> cityParamOptional = request.queryParam("city");
if (!cityParamOptional.isPresent()) {
throw new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "request param city is ERROR");
}
return Mono.just("Hello," + cityParamOptional.get());
}
}
Mono:實(shí)現(xiàn)發(fā)布者呵晨,并返回 0 或 1 個(gè)元素魏保,即單對(duì)象。Mono 是響應(yīng)流 Publisher 具有基礎(chǔ) rx 操作符摸屠∥铰蓿可以成功發(fā)布元素或者錯(cuò)誤。用 Mono 作為返回對(duì)象季二,是因?yàn)榉祷匕艘粋€(gè) ServerResponse 對(duì)象檩咱,而不是多個(gè)元素。
ServerResponse 是對(duì)響應(yīng)的封裝胯舷,可以設(shè)置響應(yīng)狀態(tài)刻蚯,響應(yīng)頭,響應(yīng)正文需纳。比如 ok 代表的是 200 響應(yīng)碼芦倒、MediaType 枚舉是代表這文本內(nèi)容類型、返回的是 String 的對(duì)象不翩。
ServerRequest 是對(duì)請(qǐng)求的封裝兵扬。從請(qǐng)求中拿出 city 的值,如果沒有的話則拋出對(duì)應(yīng)的異常口蝠。GlobalException 是封裝的全局異常器钟。
Mono.justOrEmpty():從一個(gè) Optional 對(duì)象或 null 對(duì)象中創(chuàng)建 Mono。
2.4 GlobalError 處理類
如圖:
圖片上傳失敗妙蔗,見原文 https://www.bysocket.com/archives/2272
GlobalException 全局異常類傲霸,代碼如下:
public class GlobalException extends ResponseStatusException {
public GlobalException(HttpStatus status, String message) {
super(status, message);
}
public GlobalException(HttpStatus status, String message, Throwable e) {
super(status, message, e);
}
}
GlobalErrorAttributes 全局異常屬性值類,代碼如下:
@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(request, includeStackTrace);
if (getError(request) instanceof GlobalException) {
GlobalException ex = (GlobalException) getError(request);
map.put("exception", ex.getClass().getSimpleName());
map.put("message", ex.getMessage());
map.put("status", ex.getStatus().value());
map.put("error", ex.getStatus().getReasonPhrase());
return map;
}
map.put("exception", "SystemException");
map.put("message", "System Error , Check logs!");
map.put("status", "500");
map.put("error", " System Error ");
return map;
}
}
重寫了父類 DefaultErrorAttributes 默認(rèn)錯(cuò)誤屬性類的 getErrorAttributes 獲取錯(cuò)誤屬性方法眉反,從服務(wù)請(qǐng)求封裝 ServerRequest 中獲取對(duì)應(yīng)的異常昙啄。
然后判斷是否是 GlobalException,如果是 CityHandler 服務(wù)處理類拋出的 GlobalException寸五,則返回對(duì)應(yīng)的異常的信息梳凛。
GlobalErrorWebExceptionHandler 全局異常處理類,代碼如下:
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes g, ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(g, new ResourceProperties(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(final ServerRequest request) {
final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorPropertiesMap));
}
}
代碼解析如下:
- AbstractErrorWebExceptionHandler 抽象類是用來處理全局錯(cuò)誤時(shí)進(jìn)行擴(kuò)展和實(shí)現(xiàn)
- @Order 注解標(biāo)記 AspectJ 的切面排序梳杏,值越小擁有越高的優(yōu)先級(jí)韧拒,這里設(shè)置優(yōu)先級(jí)偏高淹接。
- 構(gòu)造函數(shù)將 GlobalErrorAttributes 全局異常屬性值類設(shè)置到 AbstractErrorWebExceptionHandler 抽象類的局部變量中。
- 重寫 getRoutingFunction 方法叛溢,設(shè)置對(duì)應(yīng)的 RequestPredicates 和 Mono<ServerResponse> 服務(wù)響應(yīng)對(duì)象
- 將 GlobalErrorAttributes 的全局異常屬性值 map塑悼,設(shè)置到新的 ServerResponse 即可。
到此基本結(jié)束楷掉。Spring Boot MVC 錯(cuò)誤碼如何實(shí)戰(zhàn)厢蒜,參考地址:https://www.bysocket.com/archives/1692
2.5 運(yùn)行驗(yàn)證
在 IDEA 中執(zhí)行 Application
類啟動(dòng),任意正常模式或者 Debug 模式靖诗。然后打開瀏覽器訪問:
http://localhost:8080/hello
異常界面如下:
圖片上傳失敗郭怪,見原文 https://www.bysocket.com/archives/2272
可見,這是在 CityHandler 城市服務(wù)處理類邏輯中拋出的全局異常信息刊橘。那么正常情況會(huì)是如何?
改下 URL 颂鸿,訪問如下:
http://localhost:8080/hello?city=WenLing
正常界面如下:
圖片上傳失敗促绵,見原文 https://www.bysocket.com/archives/2272
三、小結(jié)
在 Spring 框架中沒有代表錯(cuò)誤響應(yīng)的類嘴纺,只是返回響應(yīng)對(duì)象败晴,一個(gè) Map。如果需要定義業(yè)務(wù)的錯(cuò)誤碼返回體栽渴,參考錯(cuò)誤碼如何實(shí)戰(zhàn)尖坤,參考地址:https://www.bysocket.com/archives/1692。
本文重點(diǎn)還是有別于 Spring Boot 傳統(tǒng) MVC 模式統(tǒng)一異常處理闲擦,實(shí)戰(zhàn)了 WebFlux 全局異常處理機(jī)制慢味。實(shí)戰(zhàn)中這塊擴(kuò)展需要考慮:
- 異常分層,從基類中擴(kuò)展出來
- 錯(cuò)誤碼設(shè)計(jì)分層墅冷,易擴(kuò)展纯路,比如在錯(cuò)誤碼中新增調(diào)用量字段...
代碼示例
本文示例讀者可以通過查看下面?zhèn)}庫的中的模塊工程名: 2-x-spring-boot-webflux-handling-errors:
- Github:https://github.com/JeffLi1993/springboot-learning-example
- Gitee:https://gitee.com/jeff1993/springboot-learning-example
如果您對(duì)這些感興趣,歡迎 star寞忿、follow驰唬、收藏、轉(zhuǎn)發(fā)給予支持腔彰!
參考資料
- WebFlux REST API 全局異常處理:https://www.bysocket.com/archives/2100
- https://dzone.com/articles/exception-handling-in-spring-boot-webflux-reactive