本文首先將會回顧Spring 5以前的SpringMVC異常處理機制,而後主要講解Spring Boot 2 Webflux的全局異常處理機制。java
Spring 統一異常處理有 3 種方式,分別爲:web
@ExceptionHandler
註解HandlerExceptionResolver
接口@controlleradvice
註解@ExceptionHandler
註解用於局部方法捕獲,與拋出異常的方法處於同一個Controller類:spring
@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("出錯了!");
}
}
複製代碼
如上的代碼實現,針對BuzController
拋出的NullPointerException
異常,將會捕獲局部異常,返回指定的內容。編程
HandlerExceptionResolver
接口經過實現HandlerExceptionResolver
接口,這裏咱們經過繼承SimpleMappingExceptionResolver
實現類(HandlerExceptionResolver
實現,容許將異常類名稱映射到視圖名稱,既能夠是一組給定的handlers處理程序,也能夠是DispatcherServlet中的全部handlers)定義全局異常:瀏覽器
@Component
public class CustomMvcExceptionHandler extends SimpleMappingExceptionResolver {
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();
}
}
複製代碼
如上爲示例的使用方式,咱們能夠根據各類異常定製錯誤的響應。服務器
@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);
}
}
複製代碼
和第一種方式的區別在於,ExceptionHandler
的定義和異常捕獲能夠擴展到全局。微信
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提供了一套函數式接口,能夠用來實現相似MVC的效果。咱們先接觸兩個經常使用的。mvc
Controller定義對Request的處理邏輯的方式,主要有方面:app
在WebFlux的函數式開發模式中,咱們用HandlerFunction和RouterFunction來實現上邊這兩點。
HandlerFunction
至關於Controller中的具體處理方法,輸入爲請求,輸出爲裝在Mono中的響應:
Mono<T> handle(ServerRequest var1);
複製代碼
在WebFlux中,請求和響應再也不是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse。後者是在響應式編程中使用的接口,它們提供了對非阻塞和回壓特性的支持,以及Http消息體與響應式類型Mono和Flux的轉換方法。
@Component
public class TimeHandler {
public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
String timeType = serverRequest.queryParam("type").get();
//return ...
}
}
複製代碼
如上定義了一個TimeHandler
,根據請求的參數返回當前時間。
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)的狀況下調用相同的請求地址,例如/time,它將拋出異常。 Mono和Flux APIs內置了兩個關鍵操做符,用於處理功能級別上的錯誤。
還能夠使用onErrorResume處理錯誤,fallback方法定義以下:
Mono<T> onErrorResume(Function<? super Throwable, ? extends Mono<? extends T>> fallback);
複製代碼
當出現錯誤時,咱們使用fallback方法執行替代路徑:
@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();
}
}
}
複製代碼
在如上的實現中,每當getTimeByType()
拋出異常時,將會執行咱們定義的fallback
方法。除此以外,咱們還能夠捕獲、包裝和從新拋出異常,例如做爲自定義業務異常:
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()
返回靜態默認值:
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的函數式開發模式也能夠進行全局異常處理。要作到這一點,咱們只須要自定義全局錯誤響應屬性,而且實現全局錯誤處理邏輯。
咱們的處理程序拋出的異常將自動轉換爲HTTP狀態和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;
}
//...有省略
}
複製代碼
如上的實現中,咱們對ServerException
進行了特別處理,根據傳入的ErrorCode
對象構造對應的響應。
接下來,讓咱們實現全局錯誤處理程序。爲此,Spring提供了一個方便的AbstractErrorWebExceptionHandler
類,供咱們在處理全局錯誤時進行擴展和實現:
@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
//構造函數
@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));
}
}
複製代碼
這裏將全局錯誤處理程序的順序設置爲-2。這是爲了讓它比@Order(-1)
註冊的DefaultErrorWebExceptionHandler
處理程序更高的優先級。
該errorAttributes對象將是咱們在網絡異常處理程序的構造函數傳遞一個的精確副本。理想狀況下,這應該是咱們自定義的Error Attributes類。而後,咱們清楚地代表咱們想要將全部錯誤處理請求路由到renderErrorResponse()方法。最後,咱們獲取錯誤屬性並將它們插入服務器響應主體中。
而後,它會生成一個JSON響應,其中包含錯誤,HTTP狀態和計算機客戶端異常消息的詳細信息。對於瀏覽器客戶端,它有一個whitelabel錯誤處理程序,它以HTML格式呈現相同的數據。固然,這能夠是定製的。
本文首先講了Spring 5以前的SpringMVC異常處理機制,SpringMVC統一異常處理有 3 種方式:使用 @ExceptionHandler
註解、實現 HandlerExceptionResolver
接口、使用 @controlleradvice
註解;而後經過WebFlux的函數式接口構建Web應用,講解Spring Boot 2 Webflux的函數級別和全局異常處理機制(對於Spring WebMVC風格,基於註解的方式編寫響應式的Web服務,仍然能夠經過SpringMVC統一異常處理實現)。
注:本文後半部分基本翻譯自www.baeldung.com/spring-webf…