Spring Cloud Gateway中異常處理


 最近我們的項目在考慮使用Gateway,考慮使用Spring Cloud Gateway,發現網關的異常處理和spring boot 單體應用異常處理還是有很大區別的。讓我們來回顧一下異常。

關於異常是拿來干什么的,很多人老程序員認為就是拿來我們Debug的時候排錯的,當然這一點確實是異常機制非常大的一個好處,但異常機制包含着更多的意義。

  • 關注業務實現。異常機制使得業務代碼與異常處理代碼可以分開,你可以將一些你調用數據庫操作的代碼寫在一個方法里而只需要在方法上加上throw DB相關的異常。至於如何處理它,你可以在調用該方法的時候處理或者甚至選擇不處理,而不是直接在該方法內部添加上if判斷如果數據庫操作錯誤該如何辦,這樣業務代碼會非常混亂。
  • 統一異常處理。與上一點有所聯系。我當前所在項目的實踐是,自定義業務類異常,在Controller或Service中拋出,讓后使用Spring提供的異常接口統一處理我們自己在內部拋出的異常。這樣一個異常處理架構就非常明了。
  • 程序的健壯性。如果沒有異常機制,那么來了個對空對象的某方法調用怎么辦呢?直接讓程序掛掉?這令人無法接受,當然,我們自己平時寫的一些小的東西確實是這樣,沒有處理它,讓后程序掛了。但在web框架中,可以利用異常處理機制捕獲該異常並將錯誤信息傳遞給我們然后繼續處理下個請求。所以異常對於健壯性是非常有幫助的。

異常處理(又稱為錯誤處理)功能提供了處理程序運行時出現的任何意外或異常情況的方法。異常處理使用 try、catch 和 finally 關鍵字來嘗試可能未成功的操作,處理失敗,以及在事后清理資源。異常根據意義成三種:業務、系統、代碼異常,不同的異常采用不同的處理方式。具體的什么樣的異常怎么處理就不說了。

紅線和綠線代表兩條異常路徑

1,紅線代表:請求到Gateway發生異常,可能由於后端app在啟動或者是沒啟動

2,綠線代表:請求到Gateway轉發到后端app,后端app發生異常,然后Gateway轉發后端異常到前端

紅線肯定是走Gateway自定義異常:

兩個類的代碼如下(參考:http://cxytiandi.com/blog/detail/20548):

 1 @Configuration
 2 @EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
 3 public class ExceptionHandlerConfiguration {
 4 
 5     private final ServerProperties serverProperties;
 6 
 7     private final ApplicationContext applicationContext;
 8 
 9     private final ResourceProperties resourceProperties;
10 
11     private final List<ViewResolver> viewResolvers;
12 
13     private final ServerCodecConfigurer serverCodecConfigurer;
14 
15     public ExceptionHandlerConfiguration(ServerProperties serverProperties,
16                                          ResourceProperties resourceProperties,
17                                          ObjectProvider<List<ViewResolver>> viewResolversProvider,
18                                          ServerCodecConfigurer serverCodecConfigurer,
19                                          ApplicationContext applicationContext) {
20         this.serverProperties = serverProperties;
21         this.applicationContext = applicationContext;
22         this.resourceProperties = resourceProperties;
23         this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
24         this.serverCodecConfigurer = serverCodecConfigurer;
25     }
26 
27     @Bean
28     @Order(Ordered.HIGHEST_PRECEDENCE)
29     public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
30         JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
31                 errorAttributes,
32                 this.resourceProperties,
33                 this.serverProperties.getError(),
34                 this.applicationContext);
35         exceptionHandler.setViewResolvers(this.viewResolvers);
36         exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
37         exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
38         return exceptionHandler;
39     }
 1 public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
 2 
 3     private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class);
 4 
 5     public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
 6                                 ErrorProperties errorProperties, ApplicationContext applicationContext) {
 7         super(errorAttributes, resourceProperties, errorProperties, applicationContext);
 8     }
 9 
10         /**
11          * 獲取異常屬性
12          */
13         @Override
14         protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
15             int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
16             Throwable error = super.getError(request);
17             if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
18                 code = HttpStatus.NOT_FOUND.value();
19             }
20             return response(code, this.buildMessage(request, error));
21         }
22 
23         /**
24          * 指定響應處理方法為JSON處理的方法
25          * @param errorAttributes
26          */
27         @Override
28         protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
29             return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
30         }
31 
32 
33         /**
34          * 根據code獲取對應的HttpStatus
35          * @param errorAttributes
36          */
37         @Override
38         protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
39             int statusCode = (int) errorAttributes.get("code");
40             return HttpStatus.valueOf(statusCode);
41         }
42 
43         /**
44          * 構建異常信息
45          * @param request
46          * @param ex
47          * @return
48          */
49         private String buildMessage(ServerRequest request, Throwable ex) {
50             StringBuilder message = new StringBuilder("Failed to handle request [");
51             message.append(request.methodName());
52             message.append(" ");
53             message.append(request.uri());
54             message.append("]");
55             if (ex != null) {
56                 message.append(": ");
57                 message.append(ex.getMessage());
58             }
59             return message.toString();
60         }
61 
62         /**
63          * 構建返回的JSON數據格式
64          * @param status        狀態碼
65          * @param errorMessage  異常信息
66          * @return
67          */
68         public static Map<String, Object> response(int status, String errorMessage) {
69             Map<String, Object> map = new HashMap<>();
70             map.put("code", status);
71             map.put("message", errorMessage);
72             map.put("data", null);
73             logger.error(map.toString());
74             return map;
75         }
76     }

綠線代表Gateway轉發異常

轉發的異常,肯定是springboot單體中處理的,至於spring單體中的異常是怎么處理的呢?肯定是用@ControllerAdvice去做。

1     @ExceptionHandler(value = Exception.class)
2     @ResponseBody
3     public AppResponse exceptionHandler(HttpServletRequest request, Exception e) {
4         String ip = RequestUtil.getIpAddress(request);
5         logger.info("調用者IP:" + ip);
6         String errorMessage = String.format("Url:[%s]%n{%s}", request.getRequestURL().toString(), e.getMessage());
7         logger.error(errorMessage, e);
8         return AppResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
9     }

到這里基本上可以了,大家不要試着去用Gateway去捕獲后端異常,回到最初的起點,API 網關(API Gateway)主要負責服務請求路由、組合及協議轉換,異常同樣也是一樣,Gateway只負責轉發單體應用的異常,不要試圖Gateway捕獲后端服務異常,然后再輸出給前端。感謝猿天地的一句驚醒夢中人!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM