您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Spring Cloud gateway自定義錯誤處理Handler怎么實現”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Spring Cloud gateway自定義錯誤處理Handler怎么實現”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
我們來學習和了解下GatewayExceptionHandler
有助于我們處理spring gateway和webFlux中的異常自定義處理。它繼承自ErrorWebExceptionHandler
, 類關系圖如下:
通過類上述關系圖,我們可以看到,DefaultErrorWebExceptionHandler
是ErrorWebExceptionHandler
的實現類,如果我們不自定義類異常處理器,系統將自動裝配DefaultErrorWebExceptionHandler
。
我們就來庖丁解牛,一步步地來學習下這一組類和接口。
AbstractErrorWebExceptionHandler
實現了ErrorWebExceptionHandler
接口,其中handle
方法是其核心方法。我們來看下其實現代碼:
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) { // 判斷response是否已經提交,并且調用isDisconnectedClientError方法判斷是否是客戶端斷開連接。 // 如果已經斷開連接,已經無法將消息發送給客戶端,直接Mono.error(throwable)拋出異常。 if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable)) { // exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error); this.errorAttributes.storeErrorInformation(throwable, exchange); // 創建request ServerRequest request = ServerRequest.create(exchange, this.messageReaders); return this.getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable)).flatMap((handler) -> { return handler.handle(request); }).doOnNext((response) -> { this.logError(request, response, throwable); }).flatMap((response) -> { return this.write(exchange, response); }); } else { return Mono.error(throwable); } }
通過Handle方法的代碼我們看出主要是實現了以下的事項:
判斷response是否已經提交,并且調用isDisconnectedClientError方法判斷是否是客戶端斷開連接。
如果已經斷開連接,已經無法將消息發送給客戶端,直接Mono.error(throwable)拋出異常。
將異常信息存儲到exchange中。
創建request。
獲取路由函數。
調用路由函數的handle方法。
然后執行日志記錄。
最后將response寫入到exchange中。
private boolean isDisconnectedClientError(Throwable ex) { return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage()); }
DISCONNECTED_CLIENT_EXCEPTIONS是一個集合,里面存放了一些異常信息,如果是這些異常信息, 就認為是客戶端斷開連接了。 或者通過isDisconnectedClientErrorMessage方法判斷是否是客戶端斷開連接的異常信息。
下面我們來看看isDisconnectedClientErrorMessage方法的實現。
private boolean isDisconnectedClientErrorMessage(String message) { message = message != null ? message.toLowerCase() : ""; return message.contains("broken pipe") || message.contains("connection reset by peer"); }
上述代碼的含義是:如果異常信息中包含“broken pipe”或者“connection reset by peer”,就認為是客戶端斷開連接了。
綜合起來,isDisconnectedClientError方法的含義是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含異常信息的類名或者異常信息中包含“broken pipe”或者“connection reset by peer”,就認為是客戶端斷開連接了。
而isDisconnectedClientErrorMessage方法的參數message來自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我們進一步跟蹤源碼,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源碼如下:
public static Throwable getMostSpecificCause(Throwable ex) { Throwable cause; Throwable result = ex; while(null != (cause = result.getCause()) && (result != cause)) { result = cause; } return result; }
上述代碼的含義是:如果ex.getCause()不為空,并且ex.getCause()不等于ex,就將ex.getCause()賦值給result,然后繼續執行while循環。直到ex.getCause()為空或者ex.getCause()等于ex,就返回result。
我們看到獲得路由調用的是getRoutingFunction
方法,而這個方法是一個抽象方法,需要在具體的繼承類中實現。
protected void logError(ServerRequest request, ServerResponse response, Throwable throwable) { if (logger.isDebugEnabled()) { logger.debug(request.exchange().getLogPrefix() + this.formatError(throwable, request)); } if (HttpStatus.resolve(response.rawStatusCode()) != null && response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) { logger.error(LogMessage.of(() -> { return String.format("%s 500 Server Error for %s", request.exchange().getLogPrefix(), this.formatRequest(request)); }), throwable); } }
上述代碼的含義:
如果是debug級別的日志,就以debug的方式打印異常信息。
如果response的狀態碼是500,就打印異常信息。
用到的formatError方法就是簡單的講錯誤格式化,代碼不再一一分析。
在把內容寫入的時候用到了私有方法write,我們看看write方法的代碼如下:
private Mono<? extends Void> write(ServerWebExchange exchange, ServerResponse response) { exchange.getResponse().getHeaders().setContentType(response.headers().getContentType()); return response.writeTo(exchange, new AbstractErrorWebExceptionHandler.ResponseContext()); }
上述代碼的含義是:將response的contentType設置到exchange的response中,然后調用response的writeTo方法,將response寫入到exchange中。 ResponseContext是一個內部類,主要是攜帶了外部類中的viewResolvers和messageWriters屬性。
public void afterPropertiesSet() throws Exception { if (CollectionUtils.isEmpty(this.messageWriters)) { throw new IllegalArgumentException("Property 'messageWriters' is required"); } }
afterPropertiesSet主要是通過實現InitializingBean接口,實現afterPropertiesSet方法,判斷messageWriters是否為空,如果為空就拋出異常。
protected Mono<ServerResponse> renderDefaultErrorView(BodyBuilder responseBody, Map<String, Object> error) { StringBuilder builder = new StringBuilder(); Date timestamp = (Date)error.get("timestamp"); Object message = error.get("message"); Object trace = error.get("trace"); Object requestId = error.get("requestId"); builder.append("<html><body><h3>Whitelabel Error Page</h3>").append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>[").append(requestId).append("] There was an unexpected error (type=").append(this.htmlEscape(error.get("error"))).append(", status=").append(this.htmlEscape(error.get("status"))).append(").</div>"); if (message != null) { builder.append("<div>").append(this.htmlEscape(message)).append("</div>"); } if (trace != null) { builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>"); } builder.append("</body></html>"); return responseBody.bodyValue(builder.toString()); }
renderDefaultErrorView方法主要是渲染默認的錯誤頁面,如果沒有自定義的錯誤頁面,就會使用這個方法渲染默認的錯誤頁面。
protected Mono<ServerResponse> renderErrorView(String viewName, BodyBuilder responseBody, Map<String, Object> error) { if (this.isTemplateAvailable(viewName)) { return responseBody.render(viewName, error); } else { Resource resource = this.resolveResource(viewName); return resource != null ? responseBody.body(BodyInserters.fromResource(resource)) : Mono.empty(); } }
上述代碼的含義是:
如果viewName對應的模板存在,就使用模板渲染錯誤頁面。
否則就使用靜態資源渲染錯誤頁面。 在使用資源渲染的時候,調用resolveResource方法,代碼如下:
private Resource resolveResource(String viewName) { // 獲得所有的靜態資源的路徑 String[] var2 = this.resources.getStaticLocations(); int var3 = var2.length; // 遍歷所有的靜態資源 for(int var4 = 0; var4 < var3; ++var4) { String location = var2[var4]; try { // 獲得靜態資源 Resource resource = this.applicationContext.getResource(location); // 獲得靜態資源的文件 resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return resource; } } catch (Exception var7) { } } return null; }
DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默認實現,繼承了AbstractErrorWebExceptionHandler。通過對AbstractErrorWebExceptionHandler的分析,我們知道了大致實現原理,下面我們來看看DefaultErrorWebExceptionHandler的具體實現。
在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一個抽象方法,需要子類實現。DefaultErrorWebExceptionHandler的getRoutingFunction方法的實現如下:
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse); }
我們看到代碼中調用了RouterFunctions.route和andRoute方法。RouterFunctions.route的作用是根據請求的accept頭信息,判斷是否是text/html類型,如果是就調用renderErrorView方法,否則就調用renderErrorResponse方法。
protected RequestPredicate acceptsTextHtml() { return (serverRequest) -> { try { // 獲得請求頭中的accept信息 List<MediaType> acceptedMediaTypes = serverRequest.headers().accept(); // 定義一個MediaType.ALL的MediaType對象 MediaType var10001 = MediaType.ALL; // 移除MediaType.ALL acceptedMediaTypes.removeIf(var10001::equalsTypeAndSubtype); // 根據類型和權重對MediaType進行排序 MediaType.sortBySpecificityAndQuality(acceptedMediaTypes); // 獲得acceptMediaTypes的stream對象 Stream var10000 = acceptedMediaTypes.stream(); // 獲得MediaType.TEXT_HTML的MediaType對象 var10001 = MediaType.TEXT_HTML; var10001.getClass(); // 判斷是否有MediaType.TEXT_HTML return var10000.anyMatch(var10001::isCompatibleWith); } catch (InvalidMediaTypeException var2) { return false; } }; }
acceptsTextHtml方法的作用是判斷請求頭中的accept信息是否是text/html類型。
protected Mono<ServerResponse> renderErrorView(ServerRequest request) { // 通過getErrorAttributes方法獲得錯誤信息 Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)); // 獲得錯誤狀態碼 int errorStatus = this.getHttpStatus(error); // 獲得錯誤頁面的模板名稱 BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8); // 獲得錯誤頁面的模板名稱 return Flux.just(this.getData(errorStatus).toArray(new String[0])).flatMap((viewName) -> { return this.renderErrorView(viewName, responseBody, error); }).switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled() ? this.renderDefaultErrorView(responseBody, error) : Mono.error(this.getError(request))).next(); }
其中
this.getData(errorStatus)
通過錯誤的code獲得錯誤頁面的名稱。
this.renderErrorView(viewName, responseBody, error),通過錯誤頁面的名稱,錯誤信息,錯誤狀態碼,獲得錯誤頁面的響應體。
this.renderDefaultErrorView(responseBody, error),通過錯誤信息,錯誤狀態碼,獲得默認的錯誤頁面的響應體。
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { // 通過getErrorAttributes方法獲得錯誤信息 Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL)); // 獲得錯誤狀態碼 // 設置為json格式的響應體 // 返回錯誤信息 return ServerResponse.status(this.getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(error)); }
通過上述代碼看到主要執行了以下流程:
通過getErrorAttributes方法獲得錯誤信息
獲得錯誤狀態碼
設置為json格式的響應體
返回錯誤信息
protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) { ErrorAttributeOptions options = ErrorAttributeOptions.defaults(); if (this.errorProperties.isIncludeException()) { options = options.including(new Include[]{Include.EXCEPTION}); } if (this.isIncludeStackTrace(request, mediaType)) { options = options.including(new Include[]{Include.STACK_TRACE}); } if (this.isIncludeMessage(request, mediaType)) { options = options.including(new Include[]{Include.MESSAGE}); } if (this.isIncludeBindingErrors(request, mediaType)) { options = options.including(new Include[]{Include.BINDING_ERRORS}); } return options; }
我們看到上述代碼執行了下面的流程:
獲得ErrorAttributeOptions對象
判斷是否包含異常信息
判斷是否包含堆棧信息
判斷是否包含錯誤信息
判斷是否包含綁定錯誤信息 如果包含這些信息,就將對應的信息加入到ErrorAttributeOptions對象中。而這些判斷主要是通過ErrorProperties對象中的配置來判斷的。
當認為默認的DefaultErrorWebExceptionHandler不滿足需求時,我們可以自定義自己的異常處理類。
只需要繼承DefaultErrorWebExceptionHandler類,然后重寫getErrorAttributes方法即可。
import org.springframework.context.annotation.Configuration; @Configuration @Order(-1) public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler { public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } @Override protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map<String, Object> errorAttributes = super.getErrorAttributes(request, options); errorAttributes.put("ext", "自定義異常處理類"); return errorAttributes; } }
import org.springframework.context.annotation.Configuration; @Configuration @Order(-1) public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } @Override protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map<String, Object> errorAttributes = super.getErrorAttributes(request, options); errorAttributes.put("ext", "自定義異常處理類"); return errorAttributes; } }
import org.springframework.context.annotation.Configuration; @Configuration @Order(-1) public class MyErrorWebExceptionHandler implements WebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse response = exchange.getResponse(); if (response.isCommitted()) { return Mono.error(ex); } else { response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); return response.writeWith(Mono.just(response.bufferFactory().wrap("自定義異常處理類".getBytes()))); } } }
讀到這里,這篇“Spring Cloud gateway自定義錯誤處理Handler怎么實現”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。