Spring Boot 2.x 系列教程:WebFlux REST API 全局異常處理 Error Handling

本文內(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 工程信息

工程結(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:

如果您對(duì)這些感興趣,歡迎 star寞忿、follow驰唬、收藏、轉(zhuǎn)發(fā)給予支持腔彰!

參考資料

以下專題教程也許您會(huì)有興趣

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叫编,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子霹抛,更是在濱河造成了極大的恐慌搓逾,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件上炎,死亡現(xiàn)場(chǎng)離奇詭異恃逻,居然都是意外死亡雏搂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門寇损,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凸郑,“玉大人,你說我怎么就攤上這事矛市≤搅ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵浊吏,是天一觀的道長(zhǎng)而昨。 經(jīng)常有香客問我,道長(zhǎng)找田,這世上最難降的妖魔是什么歌憨? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮墩衙,結(jié)果婚禮上务嫡,老公的妹妹穿的比我還像新娘。我一直安慰自己漆改,他們只是感情好心铃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挫剑,像睡著了一般去扣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樊破,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天愉棱,我揣著相機(jī)與錄音,去河邊找鬼捶码。 笑死羽氮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惫恼。 我是一名探鬼主播档押,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祈纯!你這毒婦竟也來了令宿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤腕窥,失蹤者是張志新(化名)和其女友劉穎粒没,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體簇爆,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癞松,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年爽撒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片响蓉。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硕勿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枫甲,到底是詐尸還是另有隱情源武,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布想幻,位于F島的核電站粱栖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脏毯。R本人自食惡果不足惜闹究,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抄沮。 院中可真熱鬧跋核,春花似錦、人聲如沸叛买。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽率挣。三九已至,卻和暖如春露戒,著一層夾襖步出監(jiān)牢的瞬間椒功,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工智什, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留动漾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓荠锭,卻偏偏與公主長(zhǎng)得像旱眯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子证九,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容