聊聊Spring?Cloud?Gateway過濾器精確控制異常返回問題
歡迎訪問我的GitHub
這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽在《Spring Cloud Gateway修改請求和響應(yīng)body的內(nèi)容》一文中,咱們通過filter成功修改請求body的內(nèi)容,當時留下個問題:在filter中如果發(fā)生異常(例如請求參數(shù)不合法),拋出異常信息的時候,調(diào)用方收到的返回碼和body都是Spring Cloud Gateway框架處理后的,調(diào)用方無法根據(jù)這些內(nèi)容知道真正的錯誤原因,如下圖:
本篇任務(wù)就是分析上述現(xiàn)象的原因,通過閱讀源碼搞清楚返回碼和響應(yīng)body生成的具體邏輯
- 這里將分析結(jié)果提前小結(jié)出來,如果您很忙碌沒太多時間卻又想知道最終原因,直接關(guān)注以下小結(jié)即可:
- Spring Cloud Gateway應(yīng)用中,有個ErrorAttributes類型的bean,它的getErrorAttributes方法返回了一個map
- 應(yīng)用拋出異常時,返回碼來自上述map的status的值,返回body是整個map序列化的結(jié)果
- 默認情況下ErrorAttributes的實現(xiàn)類是DefaultErrorAttributes
- 再看上述map的status值(也就是response的返回碼),在DefaultErrorAttributes是如何生成的:
先看異常對象是不是ResponseStatusException類型
- 如果是ResponseStatusException類型,就調(diào)用異常對象的getStatus方法作為返回值
- 如果不是ResponseStatusException類型,再看異常類有沒有ResponseStatus注解,
- 如果有,就取注解的code屬性作為返回值
- 如果異常對象既不是ResponseStatusException類型,也沒有ResponseStatus注解,就返回500
最后看map的message字段(也就是response body的message字段),在DefaultErrorAttributes是如何生成的:
- 異常對象是不是BindingResult類型
- 如果不是BindingResult類型,就看是不是ResponseStatusException類型
- 如果是,就用getReason作為返回值
- 如果也不是ResponseStatusException類型,就看異常類有沒有ResponseStatus注解,如果有就取該注解的reason屬性作為返回值
- 如果通過注解取得的reason也無效,就返回異常的getMessage字段
上述內(nèi)容就是本篇精華,但是并未包含分析過程,如果您對Spring Cloud源碼感興趣,請允許欣宸陪伴您來一次短暫的源碼閱讀之旅
Spring Cloud Gateway錯誤處理源碼
首先要看的是配置類ErrorWebFluxAutoConfiguration.java,這里面向spring注冊了兩個實例,每個都非常重要,咱們先關(guān)注第一個,也就是說ErrorWebExceptionHandler的實現(xiàn)類是DefaultErrorWebExceptionHandler:
處理異常時,會通過FluxOnErrorResume調(diào)用到這個ErrorWebExceptionHandler的handle方法處理,該方法在其父類AbstractErrorWebExceptionHandler.java中,如下圖,紅框位置的代碼是關(guān)鍵,異常返回內(nèi)容就是在這里決定的:
展開這個getRoutingFunction方法,可見會調(diào)用renderErrorResponse來處理響應(yīng):
@Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse); }
打開renderErrorResponse方法,如下所示,真相大白了!
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { // 取出所有錯誤信息 Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); // 構(gòu)造返回的所有信息 return ServerResponse // 控制返回碼 .status(getHttpStatus(error)) // 控制返回ContentType .contentType(MediaType.APPLICATION_JSON) // 控制返回內(nèi)容 .body(BodyInserters.fromValue(error)); }
通過上述代碼,咱們得到兩個重要結(jié)論:
- 返回給調(diào)用方的狀態(tài)碼,取決于getHttpStatus方法的返回值
- 返回給調(diào)用方的body,取決于error的內(nèi)容
都已經(jīng)讀到了這里,自然要看看getHttpStatus的內(nèi)部,如下所示,status來自入?yún)ⅲ?/p>
protected int getHttpStatus(Map<String, Object> errorAttributes) { return (int) errorAttributes.get("status"); }
- 至此,咱們可以得出一個結(jié)論:getErrorAttributes方法的返回值是決定返回碼和返回body的關(guān)鍵!
- 來看看這個getErrorAttributes方法的廬山真面吧,在DefaultErrorAttributes.java中(回憶剛才看ErrorWebFluxAutoConfiguration.java的時候,前面曾提到里面的東西都很重要,也包括errorAttributes方法):
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Map<String, Object> errorAttributes = this.getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE)); if (Boolean.TRUE.equals(this.includeException)) { options = options.including(new Include[]{Include.EXCEPTION}); } if (!options.isIncluded(Include.EXCEPTION)) { errorAttributes.remove("exception"); } if (!options.isIncluded(Include.STACK_TRACE)) { errorAttributes.remove("trace"); } if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) { errorAttributes.put("message", ""); } if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } return errorAttributes; }
篇幅所限,就不再展開上述代碼了,直接上結(jié)果吧:
- 返回碼來自determineHttpStatus的返回
- message字段來自determineMessage的返回打開determineHttpStatus方法,終極答案揭曉,請關(guān)注中文注釋:
private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) { // 異常對象是不是ResponseStatusException類型 return error instanceof ResponseStatusException // 如果是ResponseStatusException類型,就調(diào)用異常對象的getStatus方法作為返回值 ? ((ResponseStatusException)error).getStatus() // 如果不是ResponseStatusException類型,再看異常類有沒有ResponseStatus注解, // 如果有,就取注解的code屬性作為返回值 : (HttpStatus)responseStatusAnnotation.getValue("code", HttpStatus.class) // 如果異常對象既不是ResponseStatusException類型,也沒有ResponseStatus注解,就返回500 .orElse(HttpStatus.INTERNAL_SERVER_ERROR); }
另外,message字段的內(nèi)容也確定了:
private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) { // 異常對象是不是BindingResult類型 if (error instanceof BindingResult) { // 如果是,就用getMessage作為返回值 return error.getMessage(); } // 如果不是BindingResult類型,就看是不是ResponseStatusException類型 else if (error instanceof ResponseStatusException) { // 如果是,就用getReason作為返回值 return ((ResponseStatusException)error).getReason(); } else { // 如果也不是ResponseStatusException類型, // 就看異常類有沒有ResponseStatus注解,如果有就取該注解的reason屬性作為返回值 String reason = (String)responseStatusAnnotation.getValue("reason", String.class).orElse(""); if (StringUtils.hasText(reason)) { return reason; } else { // 如果通過注解取得的reason也無效,就返回異常的getMessage字段 return error.getMessage() != null ? error.getMessage() : ""; } } }
- 至此,源碼分析已完成,最終的返回碼和返回內(nèi)容究竟如何控制,相信聰明的您心里應(yīng)該有數(shù)了,下一篇《實戰(zhàn)篇》咱們趁熱打鐵,寫代碼試試精確控制返回碼和返回內(nèi)容
- 提前劇透,接下來的《實戰(zhàn)篇》會有以下內(nèi)容呈現(xiàn):
- 直接了當,控制返回碼和body中的error字段
- 小小攔路虎,見招拆招
- 簡單易用,通過注解控制返回信息
- 終極方案,完全定制返回內(nèi)容
到此這篇關(guān)于Spring?Cloud?Gateway過濾器精確控制異常返回(分析篇)的文章就介紹到這了,更多相關(guān)Spring?Cloud?Gateway過濾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用restTemplate.postForEntity()的問題
這篇文章主要介紹了使用restTemplate.postForEntity()的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09Spring boot + thymeleaf 后端直接給onclick函數(shù)賦值的實現(xiàn)代碼
這篇文章主要介紹了Spring boot + thymeleaf 后端直接給onclick函數(shù)賦值的實現(xiàn)代碼,需要的朋友可以參考下2017-06-06使用純Java實現(xiàn)一個WebSSH項目的示例代碼
這篇文章主要介紹了使用純Java實現(xiàn)一個WebSSH項目,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2020-03-03