欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文吃透Spring?Cloud?gateway自定義錯誤處理Handler

 更新時間:2023年03月01日 14:16:17   作者:油而不膩張大叔  
這篇文章主要為大家介紹了一文吃透Spring?Cloud?gateway自定義錯誤處理Handler方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

我們來學(xué)習(xí)和了解下GatewayExceptionHandler有助于我們處理spring gateway和webFlux中的異常自定義處理。它繼承自ErrorWebExceptionHandler, 類關(guān)系圖如下:

通過類上述關(guān)系圖,我們可以看到,DefaultErrorWebExceptionHandlerErrorWebExceptionHandler的實現(xiàn)類,如果我們不自定義類異常處理器,系統(tǒng)將自動裝配DefaultErrorWebExceptionHandler。

我們就來庖丁解牛,一步步地來學(xué)習(xí)下這一組類和接口。

AbstractErrorWebExceptionHandler

AbstractErrorWebExceptionHandler實現(xiàn)了ErrorWebExceptionHandler接口,其中handle方法是其核心方法。我們來看下其實現(xiàn)代碼:

public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
        // 判斷response是否已經(jīng)提交,并且調(diào)用isDisconnectedClientError方法判斷是否是客戶端斷開連接。
        // 如果已經(jīng)斷開連接,已經(jīng)無法將消息發(fā)送給客戶端,直接Mono.error(throwable)拋出異常。
        if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable)) {
            // exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error);
            this.errorAttributes.storeErrorInformation(throwable, exchange);
            // 創(chuàng)建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方法的代碼我們看出主要是實現(xiàn)了以下的事項:

  • 判斷response是否已經(jīng)提交,并且調(diào)用isDisconnectedClientError方法判斷是否是客戶端斷開連接。
  • 如果已經(jīng)斷開連接,已經(jīng)無法將消息發(fā)送給客戶端,直接Mono.error(throwable)拋出異常。
  • 將異常信息存儲到exchange中。
  • 創(chuàng)建request。
  • 獲取路由函數(shù)。
  • 調(diào)用路由函數(shù)的handle方法。
  • 然后執(zhí)行日志記錄。
  • 最后將response寫入到exchange中。

isDisconnectedClientError方法

private boolean isDisconnectedClientError(Throwable ex) {
        return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
}

DISCONNECTED_CLIENT_EXCEPTIONS是一個集合,里面存放了一些異常信息,如果是這些異常信息, 就認(rèn)為是客戶端斷開連接了。 或者通過isDisconnectedClientErrorMessage方法判斷是否是客戶端斷開連接的異常信息。

下面我們來看看isDisconnectedClientErrorMessage方法的實現(xiàn)。

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”,就認(rèn)為是客戶端斷開連接了。

小結(jié)

綜合起來,isDisconnectedClientError方法的含義是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含異常信息的類名或者異常信息中包含“broken pipe”或者“connection reset by peer”,就認(rèn)為是客戶端斷開連接了。

NestedExceptionUtils

而isDisconnectedClientErrorMessage方法的參數(shù)message來自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我們進一步跟蹤源碼,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源碼如下:

public static Throwable getMostSpecificCause(Throwable ex) {
    Throwable cause;
    Throwable result = ex;
    while(null != (cause = result.getCause()) &amp;&amp; (result != cause)) {
        result = cause;
    }
    return result;
}

上述代碼的含義是:如果ex.getCause()不為空,并且ex.getCause()不等于ex,就將ex.getCause()賦值給result,然后繼續(xù)執(zhí)行while循環(huán)。直到ex.getCause()為空或者ex.getCause()等于ex,就返回result。

getRoutingFunction

我們看到獲得路由調(diào)用的是getRoutingFunction方法,而這個方法是一個抽象方法,需要在具體的繼承類中實現(xiàn)。

logError

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的狀態(tài)碼是500,就打印異常信息。

用到的formatError方法就是簡單的講錯誤格式化,代碼不再一一分析。

write

在把內(nèi)容寫入的時候用到了私有方法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設(shè)置到exchange的response中,然后調(diào)用response的writeTo方法,將response寫入到exchange中。 ResponseContext是一個內(nèi)部類,主要是攜帶了外部類中的viewResolvers和messageWriters屬性。

其他的方法

afterPropertiesSet

public void afterPropertiesSet() throws Exception {
        if (CollectionUtils.isEmpty(this.messageWriters)) {
            throw new IllegalArgumentException("Property 'messageWriters' is required");
        }
}

afterPropertiesSet主要是通過實現(xiàn)InitializingBean接口,實現(xiàn)afterPropertiesSet方法,判斷messageWriters是否為空,如果為空就拋出異常。

renderDefaultErrorView

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><h2>Whitelabel Error Page</h2>").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方法主要是渲染默認(rèn)的錯誤頁面,如果沒有自定義的錯誤頁面,就會使用這個方法渲染默認(rèn)的錯誤頁面。

renderErrorView

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對應(yīng)的模板存在,就使用模板渲染錯誤頁面。
  • 否則就使用靜態(tài)資源渲染錯誤頁面。 在使用資源渲染的時候,調(diào)用resolveResource方法,代碼如下:
private Resource resolveResource(String viewName) {
        // 獲得所有的靜態(tài)資源的路徑
        String[] var2 = this.resources.getStaticLocations();
        int var3 = var2.length;
        // 遍歷所有的靜態(tài)資源
        for(int var4 = 0; var4 < var3; ++var4) {
            String location = var2[var4];
            try {
                // 獲得靜態(tài)資源
                Resource resource = this.applicationContext.getResource(location);
                // 獲得靜態(tài)資源的文件
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return resource;
                }
            } catch (Exception var7) {
            }
        }
        return null;
    }

DefaultErrorWebExceptionHandler

DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默認(rèn)實現(xiàn),繼承了AbstractErrorWebExceptionHandler。通過對AbstractErrorWebExceptionHandler的分析,我們知道了大致實現(xiàn)原理,下面我們來看看DefaultErrorWebExceptionHandler的具體實現(xiàn)。

getRoutingFunction

在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一個抽象方法,需要子類實現(xiàn)。DefaultErrorWebExceptionHandler的getRoutingFunction方法的實現(xiàn)如下:

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
}

我們看到代碼中調(diào)用了RouterFunctions.route和andRoute方法。RouterFunctions.route的作用是根據(jù)請求的accept頭信息,判斷是否是text/html類型,如果是就調(diào)用renderErrorView方法,否則就調(diào)用renderErrorResponse方法。

acceptsTextHtml

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);
                // 根據(jù)類型和權(quán)重對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類型。

renderErrorView

protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
    // 通過getErrorAttributes方法獲得錯誤信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
        // 獲得錯誤狀態(tài)碼
        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),通過錯誤頁面的名稱,錯誤信息,錯誤狀態(tài)碼,獲得錯誤頁面的響應(yīng)體。
  • this.renderDefaultErrorView(responseBody, error),通過錯誤信息,錯誤狀態(tài)碼,獲得默認(rèn)的錯誤頁面的響應(yīng)體。

renderErrorResponse方法

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    // 通過getErrorAttributes方法獲得錯誤信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
        // 獲得錯誤狀態(tài)碼
        // 設(shè)置為json格式的響應(yīng)體
        // 返回錯誤信息
        return ServerResponse.status(this.getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(error));
    }

通過上述代碼看到主要執(zhí)行了以下流程:

  • 通過getErrorAttributes方法獲得錯誤信息
  • 獲得錯誤狀態(tài)碼
  • 設(shè)置為json格式的響應(yīng)體
  • 返回錯誤信息

getErrorAttributeOptions

    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;
    }

我們看到上述代碼執(zhí)行了下面的流程:

  • 獲得ErrorAttributeOptions對象
  • 判斷是否包含異常信息
  • 判斷是否包含堆棧信息
  • 判斷是否包含錯誤信息
  • 判斷是否包含綁定錯誤信息 如果包含這些信息,就將對應(yīng)的信息加入到ErrorAttributeOptions對象中。而這些判斷主要是通過ErrorProperties對象中的配置來判斷的。

自定義自己的異常處理類

當(dāng)認(rèn)為默認(rèn)的DefaultErrorWebExceptionHandler不滿足需求時,我們可以自定義自己的異常處理類。

繼承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&lt;String, Object&gt; getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map&lt;String, Object&gt; errorAttributes = super.getErrorAttributes(request, options);
        errorAttributes.put("ext", "自定義異常處理類");
        return errorAttributes;
    }
}

繼承AbstractErrorWebExceptionHandler

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;
    }
}

繼承WebExceptionHandler

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())));
        }
    }
}

總結(jié)

總體工作流程

經(jīng)過對AbstractErrorWebExceptionHandlerDefaultErrorWebExceptionHandler中代碼的閱讀和跟蹤。我們不難看出其工作機制??紤]到有返回API方式的json錯誤信息和錯誤頁面方式的錯誤信息。所以ExceptionHandler通過request中的accept的http頭進行判斷,如果是json格式則以json的方式進行響應(yīng),否則則以錯誤頁面的方式進行響應(yīng)。而錯誤頁面的方式,通過錯誤的code獲得錯誤頁面的名稱,然后通過錯誤頁面的名稱,錯誤信息,錯誤狀態(tài)碼,獲得錯誤頁面的響應(yīng)體。

得到的經(jīng)驗

  • 異步編程和同步編程之間差異很大,特別是在對request和response進行操作的時候。
  • 源代碼充分考慮各種情況和細(xì)節(jié),在編程中值得我們?nèi)プ鰠⒖肌?/li>

以上就是一文吃透Spring Cloud gateway自定義錯誤處理Handler的詳細(xì)內(nèi)容,更多關(guān)于Spring Cloud gateway Handler的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論