Spring Cloud Gateway過濾器精確控制異常返回(實(shí)戰(zhàn)籽慢,完全定制返回body)

歡迎訪問我的GitHub

這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

在這里插入圖片描述
  • 正如上圖所示吆倦,異常發(fā)生時(shí)系統(tǒng)固定返回8個字段听诸,這就有些不夠靈活了,在一些對格式和內(nèi)容有嚴(yán)格要求的場景下蚕泽,咱們需要能夠完全控制返回碼和返回body的內(nèi)容蛇更,如下所示,只返回三個字段赛糟,每個字段都是完全為業(yè)務(wù)服務(wù)的:
{
    # 這是有具體業(yè)務(wù)含義的返回碼
    "code": "010020003",

    # 這是能精確描述錯誤原因的文本信息
    "message": "請確保請求參數(shù)中的user-id字段是有效的",
    
    # 這是常規(guī)的業(yè)務(wù)數(shù)據(jù)派任,發(fā)生異常時(shí)該字段為空
    "data": null
}
  • 今天咱們的目標(biāo)就是通過編碼定制異常發(fā)生時(shí)的返回信息,具體內(nèi)容就是上述JSON數(shù)據(jù):只有code璧南、message掌逛、data三個字段

源碼下載

名稱 鏈接 備注
項(xiàng)目主頁 https://github.com/zq2599/blog_demos 該項(xiàng)目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項(xiàng)目源碼的倉庫地址司倚,https協(xié)議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項(xiàng)目源碼的倉庫地址豆混,ssh協(xié)議
  • 這個git項(xiàng)目中有多個文件夾,本篇的源碼在<font color="blue">spring-cloud-tutorials</font>文件夾下动知,如下圖紅框所示:
在這里插入圖片描述
  • <font color="blue">spring-cloud-tutorials</font>文件夾下有多個子工程皿伺,本篇的代碼是<font color="red">gateway-change-body</font>,如下圖紅框所示:
在這里插入圖片描述

為何不用常規(guī)手段

  • 提到全局異常處理盒粮,經(jīng)驗(yàn)豐富的您應(yīng)該想到了常用的ControllerAdvice和ExceptionHandler注解修飾的全局異常處理類鸵鸥,但是Spring Cloud Gateway是基于WebFlux的,咱們之前處理異常時(shí)用到的HttpServletRequest在Spring Cloud Gateway中并不適用丹皱,因此妒穴,不能用ControllerAdvice和ExceptionHandler的手段來處理全局異常

基本思路

  • 在動手前做好充足的理論分析,寫出的代碼才能正常工作

  • 打開DefaultErrorWebExceptionHandler.java摊崭,找到renderErrorResponse方法讼油,來看看Spring Cloud Gateway原本是如何構(gòu)造異常返回內(nèi)容的:

在這里插入圖片描述
  • 此刻聰明的您應(yīng)該想到怎么做了:做個新的類繼承DefaultErrorWebExceptionHandler,覆蓋其renderErrorResponse方法呢簸,新的renderErrorResponse方法中矮台,按照實(shí)際業(yè)務(wù)需要來設(shè)置返回內(nèi)容,沒錯根时,這就是咱們的思路瘦赫,不過還要細(xì)化一下,最終具體的步驟如下:
  1. 新增一個異常類<font color="blue">CustomizeInfoException.java</font>啸箫,該類有三個字段:http返回碼耸彪、業(yè)務(wù)返回碼、業(yè)務(wù)描述信息

  2. 在返回異常的代碼位置忘苛,使用CustomizeInfoException類來拋出異常蝉娜,按照實(shí)際業(yè)務(wù)場景設(shè)置CustomizeInfoException實(shí)例的各個字段

  3. 新增MyErrorWebExceptionHandler.java唱较,繼承自DefaultErrorWebExceptionHandler,重寫了renderErrorResponse方法召川,這里面檢查異常實(shí)例是否是CustomizeInfoException類型南缓,如果是,就從其中取出http返回碼荧呐、業(yè)務(wù)返回碼汉形、業(yè)務(wù)描述信息等字段,構(gòu)造返回body的內(nèi)容倍阐,異常實(shí)例若不是CustomizeInfoException類型概疆,就保持之前的處理邏輯不變;

  4. 新增configuration類峰搪,用于將MyErrorWebExceptionHandler實(shí)例注冊到spring環(huán)境

  • 分析完畢岔冀,開始編碼吧,為了簡單起見概耻,本篇不再新增maven子工程使套,而是基于前文創(chuàng)建的子工程<font color="red">gateway-change-body</font>,在這里面繼續(xù)寫代碼鞠柄;

編碼

  • 新增異常類<font color="blue">CustomizeInfoException.java</font>:
package com.bolingcavalry.changebody.exception;

import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
public class CustomizeInfoException extends Exception {
    /**
     * http返回碼
     */
    private HttpStatus httpStatus;

    /**
     * body中的code字段(業(yè)務(wù)返回碼)
     */
    private String code;

    /**
     * body中的message字段(業(yè)務(wù)返回信息)
     */
    private String message;
}
  • 修改RequestBodyRewrite.java的apply方法侦高,這里面是在處理請求body,如果檢查到?jīng)]有<font color="blue">user-id</font>字段厌杜,就不將請求轉(zhuǎn)發(fā)到服務(wù)提供方<font color="blue">provider-hello</font>奉呛,而是返回錯誤,這里的錯誤就用CustomizeInfoException類來處理:
@Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            Map<String, Object> map = objectMapper.readValue(body, Map.class);

            // 如果請求參數(shù)中不含user-id期奔,就返回異常
            if (!map.containsKey("user-id")) {
                CustomizeInfoException customizeInfoException = new CustomizeInfoException();
                // 這里返回406侧馅,您可以按照業(yè)務(wù)需要自行調(diào)整
                customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE);

                // 這里按照業(yè)務(wù)需要自行設(shè)置code
                customizeInfoException.setCode("010020003");

                // 這里按照業(yè)務(wù)需要自行設(shè)置返回的message
                customizeInfoException.setMessage("請確保請求參數(shù)中的user-id字段是有效的");

                return Mono.error(customizeInfoException);
            }

            // 取得id
            int userId = (Integer)map.get("user-id");

            // 得到nanme后寫入map
            map.put("user-name", mockUserName(userId));

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {
            log.error("1. json process fail", ex);
            return Mono.error(new Exception("1. json process fail", ex));
        }
    }
  • 異常處理類MyErrorWebExceptionHandler.java危尿,這里有一處需要<font color="red">重點(diǎn)關(guān)注的是:</font>下面的代碼僅是參考而已呐萌,您無需拘泥于CustomizeInfoException有關(guān)的邏輯,完全能按照業(yè)務(wù)需求自由設(shè)置返回的狀態(tài)碼和body:
package com.bolingcavalry.changebody.handler;

import com.bolingcavalry.changebody.exception.CustomizeInfoException;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;

public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resources, errorProperties, applicationContext);
    }

    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        // 返回碼
        int status;
        // 最終是用responseBodyMap來生成響應(yīng)body的
        Map<String, Object> responseBodyMap = new HashMap<>();

        // 這里和父類的做法一樣谊娇,取得DefaultErrorAttributes整理出來的所有異常信息
        Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

        // 原始的異常信息可以用getError方法取得
        Throwable throwable = getError(request);

        // 如果異常類是咱們定制的肺孤,就定制
        if (throwable instanceof CustomizeInfoException) {
            CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable;
            // http返回碼、body的code字段济欢、body的message字段赠堵,這三個信息都從CustomizeInfoException實(shí)例中獲取
            status = myGatewayException.getHttpStatus().value();
            responseBodyMap.put("code", myGatewayException.getCode());
            responseBodyMap.put("message", myGatewayException.getMessage());
            responseBodyMap.put("data", null);
        } else {
            // 如果不是咱們定制的異常,就維持和父類一樣的邏輯
            // 返回碼
            status = getHttpStatus(error);
            // body內(nèi)容
            responseBodyMap.putAll(error);
        }

        return ServerResponse
                // http返回碼
                .status(status)
                // 類型和以前一樣
                .contentType(MediaType.APPLICATION_JSON)
                // 響應(yīng)body的內(nèi)容
                .body(BodyInserters.fromValue(responseBodyMap));
    }
}
  • 最后是配置類MyErrorWebFluxAutoConfiguration.java:
package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.handler.MyErrorWebExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.stream.Collectors;

@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
public class MyErrorWebFluxAutoConfiguration {

    private final ServerProperties serverProperties;

    public MyErrorWebFluxAutoConfiguration(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    @Bean
    @Order(-1)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
                                                             org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
                                                             WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
                                                             ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {

        MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(errorAttributes,
                resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(),
                this.serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}
  • 編碼完成法褥,該把程序運(yùn)行起來驗(yàn)證效果了茫叭;

驗(yàn)證

  • 啟動應(yīng)用gateway-change-body

  • 用postman發(fā)起POST請求,地址是<font color="blue">http://localhost:8081/hello/change</font>半等,如下圖揍愁,紅框2中的http返回碼是咱們代碼里設(shè)置的呐萨,紅框3顯示返回的內(nèi)容就是咱們定制的那三個字段:

在這里插入圖片描述
  • 至此,控制Spring Cloud Gateway應(yīng)用異常返回的實(shí)戰(zhàn)已經(jīng)全部完成莽囤,從源碼分析結(jié)合實(shí)戰(zhàn)演練谬擦,希望欣宸的文章能陪伴您深入了解Spring Cloud Gateway,打造出更加強(qiáng)大的網(wǎng)關(guān)應(yīng)用朽缎;

你不孤單惨远,欣宸原創(chuàng)一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數(shù)據(jù)庫+中間件系列
  6. DevOps系列

歡迎關(guān)注公眾號:程序員欣宸

微信搜索「程序員欣宸」,我是欣宸话肖,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末北秽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子最筒,更是在濱河造成了極大的恐慌羡儿,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件是钥,死亡現(xiàn)場離奇詭異掠归,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)悄泥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門虏冻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弹囚,你說我怎么就攤上這事厨相。” “怎么了鸥鹉?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵蛮穿,是天一觀的道長娇哆。 經(jīng)常有香客問我垒手,道長,這世上最難降的妖魔是什么含末? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任灸异,我火速辦了婚禮府适,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肺樟。我一直安慰自己檐春,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布么伯。 她就那樣靜靜地躺著疟暖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俐巴,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天朋贬,我揣著相機(jī)與錄音,去河邊找鬼窜骄。 笑死锦募,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邻遏。 我是一名探鬼主播糠亩,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼准验!你這毒婦竟也來了赎线?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤糊饱,失蹤者是張志新(化名)和其女友劉穎垂寥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體另锋,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滞项,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夭坪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片文判。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖室梅,靈堂內(nèi)的尸體忽然破棺而出戏仓,到底是詐尸還是另有隱情,我是刑警寧澤亡鼠,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布赏殃,位于F島的核電站,受9級特大地震影響间涵,放射性物質(zhì)發(fā)生泄漏仁热。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一浑厚、第九天 我趴在偏房一處隱蔽的房頂上張望股耽。 院中可真熱鬧,春花似錦钳幅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至册赛,卻和暖如春钠导,著一層夾襖步出監(jiān)牢的瞬間震嫉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工牡属, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留票堵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓逮栅,卻偏偏與公主長得像悴势,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子措伐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容