Spring Boot 2 Webflux的全局異常處理

SpringMVC的異常處理

Spring 統(tǒng)一異常處理有 3 種方式,分別為:

使用@ExceptionHandler注解

實現(xiàn)HandlerExceptionResolver接口

使用@controlleradvice注解

使用@ExceptionHandler注解

用于局部方法捕獲,與拋出異常的方法處于同一個Controller類:

@ControllerpublicclassBuzController{@ExceptionHandler({NullPointerException.class})publicStringexception(NullPointerException e){? ? ? ? System.out.println(e.getMessage());? ? ? ? e.printStackTrace();return"null pointer exception";? ? }@RequestMapping("test")publicvoidtest(){thrownewNullPointerException("出錯了!");? ? }}

如上的代碼實現(xiàn),針對BuzController拋出的NullPointerException異常,將會捕獲局部異常,返回指定的內容。

實現(xiàn)HandlerExceptionResolver接口

通過實現(xiàn)HandlerExceptionResolver接口,定義全局異常:

@ComponentpublicclassCustomMvcExceptionHandlerimplementsHandlerExceptionResolver{privateObjectMapper objectMapper;publicCustomMvcExceptionHandler(){? ? ? ? objectMapper =newObjectMapper();? ? }@OverridepublicModelAndViewresolveException(HttpServletRequest request, HttpServletResponse response,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Object o, Exception ex){? ? ? ? response.setStatus(200);? ? ? ? response.setContentType(MediaType.APPLICATION_JSON_VALUE);? ? ? ? response.setCharacterEncoding("UTF-8");? ? ? ? response.setHeader("Cache-Control","no-cache, must-revalidate");? ? ? ? Map map =newHashMap<>();if(exinstanceofNullPointerException) {? ? ? ? ? ? map.put("code", ResponseCode.NP_EXCEPTION);? ? ? ? }elseif(exinstanceofIndexOutOfBoundsException) {? ? ? ? ? ? map.put("code", ResponseCode.INDEX_OUT_OF_BOUNDS_EXCEPTION);? ? ? ? }else{? ? ? ? ? ? map.put("code", ResponseCode.CATCH_EXCEPTION);? ? ? ? }try{? ? ? ? ? ? map.put("data", ex.getMessage());? ? ? ? ? ? response.getWriter().write(objectMapper.writeValueAsString(map));? ? ? ? }catch(Exception e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }returnnewModelAndView();? ? }}

如上為示例的使用方式,我們可以根據各種異常定制錯誤的響應。

使用@controlleradvice注解

@ControllerAdvicepublicclassExceptionController{@ExceptionHandler(RuntimeException.class)publicModelAndViewhandlerRuntimeException(RuntimeException ex){if(exinstanceofMaxUploadSizeExceededException) {returnnewModelAndView("error").addObject("msg","文件太大!");? ? ? ? }returnnewModelAndView("error").addObject("msg","未知錯誤:"+ ex);? ? }@ExceptionHandler(Exception.class)publicModelAndViewhandlerMaxUploadSizeExceededException(Exception ex){if(ex !=null) {returnnewModelAndView("error").addObject("msg", ex);? ? ? ? }returnnewModelAndView("error").addObject("msg","未知錯誤:"+ ex);? ? }}

和第一種方式的區(qū)別在于,ExceptionHandler的定義和異常捕獲可以擴展到全局。

Spring 5 Webflux的異常處理

webflux支持mvc的注解,是一個非常便利的功能,相比較于RouteFunction,自動掃描注冊比較省事。異常處理可以沿用ExceptionHandler。如下的全局異常處理對于RestController依然生效。

@RestControllerAdvicepublicclassCustomExceptionHandler{privatefinalLog logger = LogFactory.getLog(getClass());@ExceptionHandler(Exception.class)@ResponseStatus(code = HttpStatus.OK)publicErrorCodehandleCustomException(Exception e){? ? ? ? logger.error(e.getMessage());returnnewErrorCode("e","error");? ? }}

WebFlux示例

WebFlux提供了一套函數式接口,可以用來實現(xiàn)類似MVC的效果。我們先接觸兩個常用的。

Controller定義對Request的處理邏輯的方式,主要有方面:

方法定義處理邏輯;

然后用@RequestMapping注解定義好這個方法對什么樣url進行響應。

在WebFlux的函數式開發(fā)模式中,我們用HandlerFunction和RouterFunction來實現(xiàn)上邊這兩點。

HandlerFunction

HandlerFunction相當于Controller中的具體處理方法,輸入為請求,輸出為裝在Mono中的響應:

Monohandle(ServerRequest var1);

在WebFlux中,請求和響應不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。后者是在響應式編程中使用的接口,它們提供了對非阻塞和回壓特性的支持,以及Http消息體與響應式類型Mono和Flux的轉換方法。

@ComponentpublicclassTimeHandler{publicMonogetTime(ServerRequest serverRequest){? ? ? ? String timeType = serverRequest.queryParam("type").get();//return ...}}

如上定義了一個TimeHandler,根據請求的參數返回當前時間。

RouterFunction

RouterFunction,顧名思義,路由,相當于@RequestMapping,用來判斷什么樣的url映射到那個具體的HandlerFunction。輸入為請求,輸出為Mono中的Handlerfunction:

Mono> route(ServerRequest var1);

針對我們要對外提供的功能,我們定義一個Route。

@ConfigurationpublicclassRouterConfig{privatefinalTimeHandler timeHandler;@AutowiredpublicRouterConfig(TimeHandler timeHandler){this.timeHandler = timeHandler;? ? }@BeanpublicRouterFunctiontimerRouter(){returnroute(GET("/time"), req -> timeHandler.getTime(req));? ? }}

可以看到訪問/time的GET請求,將會由TimeHandler::getTime處理。

功能級別處理異常

如果我們在沒有指定時間類型(type)的情況下調用相同的請求地址,例如/time,它將拋出異常。

Mono和Flux APIs內置了兩個關鍵操作符,用于處理功能級別上的錯誤。

使用onErrorResume處理錯誤

還可以使用onErrorResume處理錯誤,fallback方法定義如下:

MonoonErrorResume(Function> fallback);

當出現(xiàn)錯誤時,我們使用fallback方法執(zhí)行替代路徑:

@ComponentpublicclassTimeHandler{publicMonogetTime(ServerRequest serverRequest){? ? ? ? String timeType = serverRequest.queryParam("time").orElse("Now");returngetTimeByType(timeType).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)));? ? }privateMonogetTimeByType(String timeType){? ? ? ? String type = Optional.ofNullable(timeType).orElse("Now");switch(type) {case"Now":returnMono.just("Now is "+newSimpleDateFormat("HH:mm:ss").format(newDate()));case"Today":returnMono.just("Today is "+newSimpleDateFormat("yyyy-MM-dd").format(newDate()));default:returnMono.empty();? ? ? ? }? ? }}

在如上的實現(xiàn)中,每當getTimeByType()拋出異常時,將會執(zhí)行我們定義的fallback方法。除此之外,我們還可以捕獲、包裝和重新拋出異常,例如作為自定義業(yè)務異常:

publicMonogetTime(ServerRequest serverRequest){? ? ? ? String timeType = serverRequest.queryParam("time").orElse("Now");returnServerResponse.ok()? ? ? ? ? ? ? ? .body(getTimeByType(timeType)? ? ? ? ? ? ? ? ? ? ? ? .onErrorResume(e -> Mono.error(newServerException(newErrorCode(HttpStatus.BAD_REQUEST.value(),"timeType is required", e.getMessage())))), String.class);? ? }

使用onErrorReturn處理錯誤

每當發(fā)生錯誤時,我們可以使用onErrorReturn()返回靜態(tài)默認值:

publicMonogetDate(ServerRequest serverRequest){? ? ? ? String timeType = serverRequest.queryParam("time").get();returngetTimeByType(timeType)? ? ? ? ? ? ? ? .onErrorReturn("Today is "+newSimpleDateFormat("yyyy-MM-dd").format(newDate()))? ? ? ? ? ? ? ? .flatMap(s -> ServerResponse.ok()? ? ? ? ? ? ? ? ? ? ? ? .contentType(MediaType.TEXT_PLAIN).syncBody(s));? ? }

全局異常處理

如上的配置是在方法的級別處理異常,如同對注解的Controller全局異常處理一樣,WebFlux的函數式開發(fā)模式也可以進行全局異常處理。要做到這一點,我們只需要自定義全局錯誤響應屬性,并且實現(xiàn)全局錯誤處理邏輯。

我們的處理程序拋出的異常將自動轉換為HTTP狀態(tài)和JSON錯誤正文。要自定義這些,我們可以簡單地擴展DefaultErrorAttributes類并覆蓋其getErrorAttributes()方法:

@ComponentpublicclassGlobalErrorAttributesextendsDefaultErrorAttributes{publicGlobalErrorAttributes(){super(false);? ? }@OverridepublicMapgetErrorAttributes(ServerRequest request,booleanincludeStackTrace){returnassembleError(request);? ? }privateMapassembleError(ServerRequest request){? ? ? ? Map errorAttributes =newLinkedHashMap<>();? ? ? ? Throwable error = getError(request);if(errorinstanceofServerException) {? ? ? ? ? ? errorAttributes.put("code", ((ServerException) error).getCode().getCode());? ? ? ? ? ? errorAttributes.put("data", error.getMessage());? ? ? ? }else{? ? ? ? ? ? errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR);? ? ? ? ? ? errorAttributes.put("data","INTERNAL SERVER ERROR");? ? ? ? }returnerrorAttributes;? ? }//...有省略}

如上的實現(xiàn)中,我們對ServerException進行了特別處理,根據傳入的ErrorCode對象構造對應的響應。

接下來,讓我們實現(xiàn)全局錯誤處理程序。為此,Spring提供了一個方便的AbstractErrorWebExceptionHandler類,供我們在處理全局錯誤時進行擴展和實現(xiàn):

@Component@Order(-2)publicclassGlobalErrorWebExceptionHandlerextendsAbstractErrorWebExceptionHandler{//構造函數@OverrideprotectedRouterFunctiongetRoutingFunction(finalErrorAttributes errorAttributes){returnRouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);? ? }privateMonorenderErrorResponse(finalServerRequest request){finalMap errorPropertiesMap = getErrorAttributes(request,true);returnServerResponse.status(HttpStatus.OK)? ? ? ? ? ? ? ? .contentType(MediaType.APPLICATION_JSON_UTF8)? ? ? ? ? ? ? ? .body(BodyInserters.fromObject(errorPropertiesMap));? ? }}

這里將全局錯誤處理程序的順序設置為-2。這是為了讓它比@Order(-1)注冊的DefaultErrorWebExceptionHandler處理程序更高的優(yōu)先級。

該errorAttributes對象將是我們在網絡異常處理程序的構造函數傳遞一個的精確副本。理想情況下,這應該是我們自定義的Error Attributes類。然后,我們清楚地表明我們想要將所有錯誤處理請求路由到renderErrorResponse()方法。最后,我們獲取錯誤屬性并將它們插入服務器響應主體中。

然后,它會生成一個JSON響應,其中包含錯誤,HTTP狀態(tài)和計算機客戶端異常消息的詳細信息。對于瀏覽器客戶端,它有一個whitelabel錯誤處理程序,它以HTML格式呈現(xiàn)相同的數據。當然,這可以是定制的。

小結

本文首先講了Spring 5之前的SpringMVC異常處理機制,SpringMVC統(tǒng)一異常處理有 3 種方式:使用@ExceptionHandler注解、實現(xiàn)HandlerExceptionResolver接口、使用@controlleradvice注解;然后通過WebFlux的函數式接口構建Web應用,講解Spring Boot 2 Webflux的函數級別和全局異常處理機制(對于Spring WebMVC風格,基于注解的方式編寫響應式的Web服務,仍然可以通過SpringMVC統(tǒng)一異常處理實現(xiàn))。

在此我向大家推薦一個架構學習交流群。交流學習群號:938837867 暗號:555 里面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務架構的原理,JVM性能優(yōu)化、分布式架構等這些成為架構師必備

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容