您好,登錄后才能下訂單哦!
小編給大家分享一下Spring Boot2中如何使用Webflux進(jìn)行全局異常處理,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
SpringMVC的異常處理
Spring 統(tǒng)一異常處理有 3 種方式,分別為:
使用 @ExceptionHandler 注解
實(shí)現(xiàn) HandlerExceptionResolver 接口
使用 @controlleradvice 注解
使用 @ExceptionHandler 注解
用于局部方法捕獲,與拋出異常的方法處于同一個(gè)Controller類:
@Controller public class BuzController { @ExceptionHandler({NullPointerException.class}) public String exception(NullPointerException e) { System.out.println(e.getMessage()); e.printStackTrace(); return "null pointer exception"; } @RequestMapping("test") public void test() { throw new NullPointerException("出錯(cuò)了!"); } }
如上的代碼實(shí)現(xiàn),針對(duì) BuzController 拋出的 NullPointerException 異常,將會(huì)捕獲局部異常,返回指定的內(nèi)容。
實(shí)現(xiàn) HandlerExceptionResolver 接口
通過(guò)實(shí)現(xiàn) HandlerExceptionResolver 接口,定義全局異常:
@Component public class CustomMvcExceptionHandler implements HandlerExceptionResolver { private ObjectMapper objectMapper; public CustomMvcExceptionHandler() { objectMapper = new ObjectMapper(); } @Override public ModelAndView resolveException(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<String, Object> map = new HashMap<>(); if (ex instanceof NullPointerException) { map.put("code", ResponseCode.NP_EXCEPTION); } else if (ex instanceof IndexOutOfBoundsException) { 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(); } return new ModelAndView(); } }
如上為示例的使用方式,我們可以根據(jù)各種異常定制錯(cuò)誤的響應(yīng)。
使用 @controlleradvice 注解
@ControllerAdvice public class ExceptionController { @ExceptionHandler(RuntimeException.class) public ModelAndView handlerRuntimeException(RuntimeException ex) { if (ex instanceof MaxUploadSizeExceededException) { return new ModelAndView("error").addObject("msg", "文件太大!"); } return new ModelAndView("error").addObject("msg", "未知錯(cuò)誤:" + ex); } @ExceptionHandler(Exception.class) public ModelAndView handlerMaxUploadSizeExceededException(Exception ex) { if (ex != null) { return new ModelAndView("error").addObject("msg", ex); } return new ModelAndView("error").addObject("msg", "未知錯(cuò)誤:" + ex); } }
和第一種方式的區(qū)別在于, ExceptionHandler 的定義和異常捕獲可以擴(kuò)展到全局。
Spring 5 Webflux的異常處理
webflux支持mvc的注解,是一個(gè)非常便利的功能,相比較于RouteFunction,自動(dòng)掃描注冊(cè)比較省事。異常處理可以沿用ExceptionHandler。如下的全局異常處理對(duì)于RestController依然生效。
@RestControllerAdvice public class CustomExceptionHandler { private final Log logger = LogFactory.getLog(getClass()); @ExceptionHandler(Exception.class) @ResponseStatus(code = HttpStatus.OK) public ErrorCode handleCustomException(Exception e) { logger.error(e.getMessage()); return new ErrorCode("e","error" ); } }
WebFlux示例
WebFlux提供了一套函數(shù)式接口,可以用來(lái)實(shí)現(xiàn)類似MVC的效果。我們先接觸兩個(gè)常用的。
Controller定義對(duì)Request的處理邏輯的方式,主要有方面:
方法定義處理邏輯;
然后用@RequestMapping注解定義好這個(gè)方法對(duì)什么樣url進(jìn)行響應(yīng)。
在WebFlux的函數(shù)式開(kāi)發(fā)模式中,我們用HandlerFunction和RouterFunction來(lái)實(shí)現(xiàn)上邊這兩點(diǎn)。
HandlerFunction
HandlerFunction 相當(dāng)于Controller中的具體處理方法,輸入為請(qǐng)求,輸出為裝在Mono中的響應(yīng):
Mono<T> handle(ServerRequest var1);
在WebFlux中,請(qǐng)求和響應(yīng)不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。后者是在響應(yīng)式編程中使用的接口,它們提供了對(duì)非阻塞和回壓特性的支持,以及Http消息體與響應(yīng)式類型Mono和Flux的轉(zhuǎn)換方法。
@Component public class TimeHandler { public Mono<ServerResponse> getTime(ServerRequest serverRequest) { String timeType = serverRequest.queryParam("type").get(); //return ... } }
如上定義了一個(gè) TimeHandler ,根據(jù)請(qǐng)求的參數(shù)返回當(dāng)前時(shí)間。
RouterFunction
RouterFunction ,顧名思義,路由,相當(dāng)于 @RequestMapping ,用來(lái)判斷什么樣的url映射到那個(gè)具體的 HandlerFunction 。輸入為請(qǐng)求,輸出為Mono中的 Handlerfunction :
Mono<HandlerFunction<T>> route(ServerRequest var1);
針對(duì)我們要對(duì)外提供的功能,我們定義一個(gè)Route。
@Configuration public class RouterConfig { private final TimeHandler timeHandler; @Autowired public RouterConfig(TimeHandler timeHandler) { this.timeHandler = timeHandler; } @Bean public RouterFunction<ServerResponse> timerRouter() { return route(GET("/time"), req -> timeHandler.getTime(req)); } }
可以看到訪問(wèn)/time的GET請(qǐng)求,將會(huì)由 TimeHandler::getTime 處理。
功能級(jí)別處理異常
如果我們?cè)跊](méi)有指定時(shí)間類型(type)的情況下調(diào)用相同的請(qǐng)求地址,例如/time,它將拋出異常。
Mono和Flux APIs內(nèi)置了兩個(gè)關(guān)鍵操作符,用于處理功能級(jí)別上的錯(cuò)誤。
使用onErrorResume處理錯(cuò)誤
還可以使用onErrorResume處理錯(cuò)誤,fallback方法定義如下:
Mono<T> onErrorResume(Function<? super Throwable, ? extends Mono<? extends T>> fallback);
當(dāng)出現(xiàn)錯(cuò)誤時(shí),我們使用fallback方法執(zhí)行替代路徑:
@Component public class TimeHandler { public Mono<ServerResponse> getTime(ServerRequest serverRequest) { String timeType = serverRequest.queryParam("time").orElse("Now"); return getTimeByType(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))); } private Mono<String> getTimeByType(String timeType) { String type = Optional.ofNullable(timeType).orElse( "Now" ); switch (type) { case "Now": return Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date())); case "Today": return Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())); default: return Mono.empty(); } } }
在如上的實(shí)現(xiàn)中,每當(dāng) getTimeByType() 拋出異常時(shí),將會(huì)執(zhí)行我們定義的 fallback 方法。除此之外,我們還可以捕獲、包裝和重新拋出異常,例如作為自定義業(yè)務(wù)異常:
public Mono<ServerResponse> getTime(ServerRequest serverRequest) { String timeType = serverRequest.queryParam("time").orElse("Now"); return ServerResponse.ok() .body(getTimeByType(timeType) .onErrorResume(e -> Mono.error(new ServerException(new ErrorCode(HttpStatus.BAD_REQUEST.value(), "timeType is required", e.getMessage())))), String.class); }
使用onErrorReturn處理錯(cuò)誤
每當(dāng)發(fā)生錯(cuò)誤時(shí),我們可以使用 onErrorReturn() 返回靜態(tài)默認(rèn)值:
public Mono<ServerResponse> getDate(ServerRequest serverRequest) { String timeType = serverRequest.queryParam("time").get(); return getTimeByType(timeType) .onErrorReturn("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())) .flatMap(s -> ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN).syncBody(s)); }
全局異常處理
如上的配置是在方法的級(jí)別處理異常,如同對(duì)注解的Controller全局異常處理一樣,WebFlux的函數(shù)式開(kāi)發(fā)模式也可以進(jìn)行全局異常處理。要做到這一點(diǎn),我們只需要自定義全局錯(cuò)誤響應(yīng)屬性,并且實(shí)現(xiàn)全局錯(cuò)誤處理邏輯。
我們的處理程序拋出的異常將自動(dòng)轉(zhuǎn)換為HTTP狀態(tài)和JSON錯(cuò)誤正文。要自定義這些,我們可以簡(jiǎn)單地?cái)U(kuò)展 DefaultErrorAttributes 類并覆蓋其 getErrorAttributes() 方法:
@Component public class GlobalErrorAttributes extends DefaultErrorAttributes { public GlobalErrorAttributes() { super(false); } @Override public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { return assembleError(request); } private Map<String, Object> assembleError(ServerRequest request) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); Throwable error = getError(request); if (error instanceof ServerException) { 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"); } return errorAttributes; } //...有省略 }
如上的實(shí)現(xiàn)中,我們對(duì) ServerException 進(jìn)行了特別處理,根據(jù)傳入的 ErrorCode 對(duì)象構(gòu)造對(duì)應(yīng)的響應(yīng)。
接下來(lái),讓我們實(shí)現(xiàn)全局錯(cuò)誤處理程序。為此,Spring提供了一個(gè)方便的 AbstractErrorWebExceptionHandler 類,供我們?cè)谔幚砣皱e(cuò)誤時(shí)進(jìn)行擴(kuò)展和實(shí)現(xiàn):
@Component @Order(-2) public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { //構(gòu)造函數(shù) @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, true); return ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(errorPropertiesMap)); } }
這里將全局錯(cuò)誤處理程序的順序設(shè)置為-2。這是為了讓它比 @Order(-1) 注冊(cè)的 DefaultErrorWebExceptionHandler 處理程序更高的優(yōu)先級(jí)。
該errorAttributes對(duì)象將是我們?cè)诰W(wǎng)絡(luò)異常處理程序的構(gòu)造函數(shù)傳遞一個(gè)的精確副本。理想情況下,這應(yīng)該是我們自定義的Error Attributes類。然后,我們清楚地表明我們想要將所有錯(cuò)誤處理請(qǐng)求路由到renderErrorResponse()方法。最后,我們獲取錯(cuò)誤屬性并將它們插入服務(wù)器響應(yīng)主體中。
然后,它會(huì)生成一個(gè)JSON響應(yīng),其中包含錯(cuò)誤,HTTP狀態(tài)和計(jì)算機(jī)客戶端異常消息的詳細(xì)信息。對(duì)于瀏覽器客戶端,它有一個(gè)whitelabel錯(cuò)誤處理程序,它以HTML格式呈現(xiàn)相同的數(shù)據(jù)。當(dāng)然,這可以是定制的。
看完了這篇文章,相信你對(duì)“Spring Boot2中如何使用Webflux進(jìn)行全局異常處理”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。