Spring Cloud Gateway 全局通用異常處理
在傳統 Spring Boot 應用中, 我們 @ControllerAdvice 來處理全局的異常,進行統一包裝返回
// 摘至 spring cloud alibaba console 模塊處理
@ControllerAdvice
public class ConsoleExceptionHandler {
@ExceptionHandler(AccessException.class)
private ResponseEntity<String> handleAccessException(AccessException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
}
}
例如: ③ 處應用調用數據庫異常,通過 @ControllerAdvice 包裝異常請求響應給客戶端
但在微服務架構下, 例如 ② 處 網關調用業務微服務失敗(轉發失敗、調用異常、轉發失敗),在應用設置的 @ControllerAdvice 將失效,因爲流量根本沒有轉發到應用上處理。
模擬所有路由斷言都不匹配 404 , 和 spring boot 默認保持一致的錯誤輸出頁面。 顯然我們在網關同樣配置 @ControllerAdvice 是不能解決問題,因爲 spring cloud gateway 是基於 webflux 反應式編程。
網關默認處理流程
ExceptionHandlingWebHandler
作爲spring cloud gateway
最核心WebHandler
的一部分會進行異常處理的過濾:下面是源碼:
public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
private final List<WebExceptionHandler> exceptionHandlers;
public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
super(delegate);
this.exceptionHandlers = Collections.unmodifiableList(new ArrayList(handlers));
}
public List<WebExceptionHandler> getExceptionHandlers() {
return this.exceptionHandlers;
}
public Mono<Void> handle(ServerWebExchange exchange) {
Mono completion;
try {
completion = super.handle(exchange);
} catch (Throwable var5) {
completion = Mono.error(var5);
}
WebExceptionHandler handler;
// 獲取全局的 WebExceptionHandler 執行
for(Iterator var3 = this.exceptionHandlers.iterator(); var3.hasNext(); completion = completion.onErrorResume((ex) -> {
return handler.handle(exchange, ex);
})) {
handler = (WebExceptionHandler)var3.next();
}
return completion;
}
}
- 默認實現是
DefaultErrorWebExceptionHandler
public class DefaultErrorWebExceptionHandler {
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
// 根據客戶端 `accpet` 請求頭決定返回什麼資源,如上瀏覽器返回的是 頁面
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}
}
// 模擬指定 `accpet` 情況
curl --location --request GET 'http://localhost:9999/adminx/xx' \ 18:09:23
--header 'Accept: application/json'
{"timestamp":"2020-05-24 18:09:24","path":"/adminx/xx","status":404,"error":"Not Found","message":null,"requestId":"083c48e3-2"}⏎
解決方法
重寫 ErrorWebExceptionHandler
/**
* @author lengleng
* @date 2020/5/23
* <p>
* 網關異常通用處理器,只作用在webflux 環境下 , 優先級低於 {@link ResponseStatusExceptionHandler} 執行
*/
@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
// header set_json響應
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
//是否響應狀態異常
if (ex instanceof ResponseStatusException) {
response.setStatusCode(((ResponseStatusException) ex).getStatus());
}
return response
.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
//返回json異常原因給前端
return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
} catch (JsonProcessingException e) {
log.warn("Error writing response", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
鏈接:https://juejin.im/post/5ecf06bbf265da76bd1ac76a
鏈接:https://juejin.im/post/5bbad1405188255c4a7137e1
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
1