詳解Spring Boot2 Webflux的全局異常處理
本文首先將會回顧Spring 5之前的SpringMVC異常處理機制,然后主要講解Spring Boot 2 Webflux的全局異常處理機制。
SpringMVC的異常處理
Spring 統(tǒng)一異常處理有 3 種方式,分別為:
- 使用 @ExceptionHandler 注解
- 實現(xiàn) HandlerExceptionResolver 接口
- 使用 @controlleradvice 注解
使用 @ExceptionHandler 注解
用于局部方法捕獲,與拋出異常的方法處于同一個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("出錯了!");
}
}
如上的代碼實現(xiàn),針對 BuzController 拋出的 NullPointerException 異常,將會捕獲局部異常,返回指定的內(nèi)容。
實現(xiàn) HandlerExceptionResolver 接口
通過實現(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ù)各種異常定制錯誤的響應(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", "未知錯誤:" + 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", "未知錯誤:" + ex);
}
}
和第一種方式的區(qū)別在于, ExceptionHandler 的定義和異常捕獲可以擴展到全局。
Spring 5 Webflux的異常處理
webflux支持mvc的注解,是一個非常便利的功能,相比較于RouteFunction,自動掃描注冊比較省事。異常處理可以沿用ExceptionHandler。如下的全局異常處理對于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ù)式接口,可以用來實現(xiàn)類似MVC的效果。我們先接觸兩個常用的。
Controller定義對Request的處理邏輯的方式,主要有方面:
- 方法定義處理邏輯;
- 然后用@RequestMapping注解定義好這個方法對什么樣url進行響應(yīng)。
在WebFlux的函數(shù)式開發(fā)模式中,我們用HandlerFunction和RouterFunction來實現(xiàn)上邊這兩點。
HandlerFunction
HandlerFunction 相當于Controller中的具體處理方法,輸入為請求,輸出為裝在Mono中的響應(yīng):
Mono<T> handle(ServerRequest var1);
在WebFlux中,請求和響應(yīng)不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。后者是在響應(yīng)式編程中使用的接口,它們提供了對非阻塞和回壓特性的支持,以及Http消息體與響應(yīng)式類型Mono和Flux的轉(zhuǎn)換方法。
@Component
public class TimeHandler {
public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
String timeType = serverRequest.queryParam("type").get();
//return ...
}
}
如上定義了一個 TimeHandler ,根據(jù)請求的參數(shù)返回當前時間。
RouterFunction
RouterFunction ,顧名思義,路由,相當于 @RequestMapping ,用來判斷什么樣的url映射到那個具體的 HandlerFunction 。輸入為請求,輸出為Mono中的 Handlerfunction :
Mono<HandlerFunction<T>> route(ServerRequest var1);
針對我們要對外提供的功能,我們定義一個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));
}
}
可以看到訪問/time的GET請求,將會由 TimeHandler::getTime 處理。
功能級別處理異常
如果我們在沒有指定時間類型(type)的情況下調(diào)用相同的請求地址,例如/time,它將拋出異常。
Mono和Flux APIs內(nèi)置了兩個關(guān)鍵操作符,用于處理功能級別上的錯誤。
使用onErrorResume處理錯誤
還可以使用onErrorResume處理錯誤,fallback方法定義如下:
Mono<T> onErrorResume(Function<? super Throwable, ? extends Mono<? extends T>> fallback);
當出現(xiàn)錯誤時,我們使用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();
}
}
}
在如上的實現(xiàn)中,每當 getTimeByType() 拋出異常時,將會執(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處理錯誤
每當發(fā)生錯誤時,我們可以使用 onErrorReturn() 返回靜態(tài)默認值:
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));
}
全局異常處理
如上的配置是在方法的級別處理異常,如同對注解的Controller全局異常處理一樣,WebFlux的函數(shù)式開發(fā)模式也可以進行全局異常處理。要做到這一點,我們只需要自定義全局錯誤響應(yīng)屬性,并且實現(xiàn)全局錯誤處理邏輯。
我們的處理程序拋出的異常將自動轉(zhuǎn)換為HTTP狀態(tài)和JSON錯誤正文。要自定義這些,我們可以簡單地擴展 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;
}
//...有省略
}
如上的實現(xiàn)中,我們對 ServerException 進行了特別處理,根據(jù)傳入的 ErrorCode 對象構(gòu)造對應(yīng)的響應(yīng)。
接下來,讓我們實現(xiàn)全局錯誤處理程序。為此,Spring提供了一個方便的 AbstractErrorWebExceptionHandler 類,供我們在處理全局錯誤時進行擴展和實現(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));
}
}
這里將全局錯誤處理程序的順序設(shè)置為-2。這是為了讓它比 @Order(-1) 注冊的 DefaultErrorWebExceptionHandler 處理程序更高的優(yōu)先級。
該errorAttributes對象將是我們在網(wǎng)絡(luò)異常處理程序的構(gòu)造函數(shù)傳遞一個的精確副本。理想情況下,這應(yīng)該是我們自定義的Error Attributes類。然后,我們清楚地表明我們想要將所有錯誤處理請求路由到renderErrorResponse()方法。最后,我們獲取錯誤屬性并將它們插入服務(wù)器響應(yīng)主體中。
然后,它會生成一個JSON響應(yīng),其中包含錯誤,HTTP狀態(tài)和計算機客戶端異常消息的詳細信息。對于瀏覽器客戶端,它有一個whitelabel錯誤處理程序,它以HTML格式呈現(xiàn)相同的數(shù)據(jù)。當然,這可以是定制的。
小結(jié)
本文首先講了Spring 5之前的SpringMVC異常處理機制,SpringMVC統(tǒng)一異常處理有 3 種方式:使用 @ExceptionHandler 注解、實現(xiàn) HandlerExceptionResolver 接口、使用 @controlleradvice 注解;然后通過WebFlux的函數(shù)式接口構(gòu)建Web應(yīng)用,講解Spring Boot 2 Webflux的函數(shù)級別和全局異常處理機制(對于Spring WebMVC風格,基于注解的方式編寫響應(yīng)式的Web服務(wù),仍然可以通過SpringMVC統(tǒng)一異常處理實現(xiàn))。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- SpringBoot3中Spring?WebFlux?SSE服務(wù)器發(fā)送事件的實現(xiàn)步驟
- SpringBoot深入分析webmvc和webflux的區(qū)別
- springboot webflux 過濾器(使用RouterFunction實現(xiàn))
- SpringBoot之webflux全面解析
- Springboot WebFlux集成Spring Security實現(xiàn)JWT認證的示例
- 解決spring-boot2.0.6中webflux無法獲得請求IP的問題
- Spring?Boot?3.4.3?基于?Spring?WebFlux?實現(xiàn)?SSE?功能(代碼示例)
相關(guān)文章
Java實現(xiàn)經(jīng)典游戲Flappy Bird的示例代碼
Flappy?Bird是13年紅極一時的小游戲,即摁上鍵控制鳥的位置穿過管道間的縫隙。本文將用Java語言實現(xiàn)這一經(jīng)典的游戲,需要的可以參考一下2022-02-02
Mybatis通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換
這篇文章主要為大家詳細介紹了Mybatis如何通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-12-12
Java的Cglib動態(tài)代理實現(xiàn)方式詳解
spring+hibernate 兩種整合方式配置文件的方法

