Spring Cloud實(shí)戰(zhàn)小貼士:Zuul統(tǒng)一異常處理(一)

在上一篇《Spring Cloud源碼分析(四)Zuul:核心過濾器》一文中,我們?cè)敿?xì)介紹了Spring Cloud Zuul中自己實(shí)現(xiàn)的一些核心過濾器夺谁,以及這些過濾器在請(qǐng)求生命周期中的不同作用菜职。我們會(huì)發(fā)現(xiàn)在這些核心過濾器中并沒有實(shí)現(xiàn)error階段的過濾器。那么這些過濾器可以用來做什么呢旗闽?接下來酬核,本文將介紹如何利用error過濾器來實(shí)現(xiàn)統(tǒng)一的異常處理。

過濾器中拋出異常的問題

首先适室,我們可以來看看默認(rèn)情況下嫡意,過濾器中拋出異常Spring Cloud Zuul會(huì)發(fā)生什么現(xiàn)象。我們創(chuàng)建一個(gè)pre類型的過濾器捣辆,并在該過濾器的run方法實(shí)現(xiàn)中拋出一個(gè)異常蔬螟。比如下面的實(shí)現(xiàn),在run方法中調(diào)用的doSomething方法將拋出RuntimeException異常汽畴。

public class ThrowExceptionFilter extends ZuulFilter  {

    private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        log.info("This is a pre filter, it will throw a RuntimeException");
        doSomething();
        return null;
    }

    private void doSomething() {
        throw new RuntimeException("Exist some errors...");
    }
  
}

運(yùn)行網(wǎng)關(guān)程序并訪問某個(gè)路由請(qǐng)求旧巾,此時(shí)我們會(huì)發(fā)現(xiàn):在API網(wǎng)關(guān)服務(wù)的控制臺(tái)中輸出了ThrowExceptionFilter的過濾邏輯中的日志信息,但是并沒有輸出任何異常信息忍些,同時(shí)發(fā)起的請(qǐng)求也沒有獲得任何響應(yīng)結(jié)果鲁猩。為什么會(huì)出現(xiàn)這樣的情況呢?我們又該如何在過濾器中處理異常呢罢坝?

解決方案一:嚴(yán)格的try-catch處理

回想一下廓握,我們?cè)谏弦还?jié)中介紹的所有核心過濾器,是否還記得有一個(gè)post過濾器SendErrorFilter是用來處理異常信息的嘁酿?根據(jù)正常的處理流程隙券,該過濾器會(huì)處理異常信息,那么這里沒有出現(xiàn)任何異常信息說明很有可能就是這個(gè)過濾器沒有被執(zhí)行闹司。所以娱仔,我們不妨來詳細(xì)看看SendErrorFiltershouldFilter函數(shù):

public boolean shouldFilter() {
    RequestContext ctx = RequestContext.getCurrentContext();
    return ctx.containsKey("error.status_code") && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}

可以看到該方法的返回值中有一個(gè)重要的判斷依據(jù)ctx.containsKey("error.status_code"),也就是說請(qǐng)求上下文中必須有error.status_code參數(shù)游桩,我們實(shí)現(xiàn)的ThrowExceptionFilter中并沒有設(shè)置這個(gè)參數(shù)拟枚,所以自然不會(huì)進(jìn)入SendErrorFilter過濾器的處理邏輯薪铜。那么我們要如何用這個(gè)參數(shù)呢?我們可以看一下route類型的幾個(gè)過濾器恩溅,由于這些過濾器會(huì)對(duì)外發(fā)起請(qǐng)求隔箍,所以肯定會(huì)有異常需要處理,比如RibbonRoutingFilterrun方法實(shí)現(xiàn)如下:

public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    this.helper.addIgnoredHeaders();
    try {
        RibbonCommandContext commandContext = buildCommandContext(context);
        ClientHttpResponse response = forward(commandContext);
        setResponse(response);
        return response;
    }
    catch (ZuulException ex) {
        context.set(ERROR_STATUS_CODE, ex.nStatusCode);
        context.set("error.message", ex.errorCause);
        context.set("error.exception", ex);
    }
    catch (Exception ex) {
        context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        context.set("error.exception", ex);
    }
    return null;
}

可以看到脚乡,整個(gè)發(fā)起請(qǐng)求的邏輯都采用了try-catch塊處理蜒滩。在catch異常的處理邏輯中并沒有做任何輸出操作,而是往請(qǐng)求上下文中添加一些error相關(guān)的參數(shù)奶稠,主要有下面三個(gè)參數(shù):

  • error.status_code:錯(cuò)誤編碼
  • error.exceptionException異常對(duì)象
  • error.message:錯(cuò)誤信息

其中俯艰,error.status_code參數(shù)就是SendErrorFilter過濾器用來判斷是否需要執(zhí)行的重要參數(shù)。分析到這里锌订,實(shí)現(xiàn)異常處理的大致思路就開始明朗了竹握,我們可以參考RibbonRoutingFilter的實(shí)現(xiàn)對(duì)ThrowExceptionFilterrun方法做一些異常處理的改造,具體如下:

public Object run() {
    log.info("This is a pre filter, it will throw a RuntimeException");
    RequestContext ctx = RequestContext.getCurrentContext();
    try {
        doSomething();
    } catch (Exception e) {
        ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        ctx.set("error.exception", e);
    }
    return null;
}

通過上面的改造之后辆飘,我們?cè)賴L試訪問之前的接口啦辐,這個(gè)時(shí)候我們可以得到如下響應(yīng)內(nèi)容:

{
  "timestamp": 1481674980376,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "java.lang.RuntimeException",
  "message": "Exist some errors..."
}

此時(shí),我們的異常信息已經(jīng)被SendErrorFilter過濾器正常處理并返回給客戶端了蜈项,同時(shí)在網(wǎng)關(guān)的控制臺(tái)中也輸出了異常信息芹关。從返回的響應(yīng)信息中,我們可以看到幾個(gè)我們之前設(shè)置在請(qǐng)求上下文中的內(nèi)容紧卒,它們的對(duì)應(yīng)關(guān)系如下:

  • status:對(duì)應(yīng)error.status_code參數(shù)的值
  • exception:對(duì)應(yīng)error.exception參數(shù)中Exception的類型
  • message:對(duì)應(yīng)error.exception參數(shù)中Exceptionmessage信息侥衬。對(duì)于message的信息,我們?cè)谶^濾器中還可以通過ctx.set("error.message", "自定義異常消息");來定義更友好的錯(cuò)誤信息跑芳。SendErrorFilter會(huì)優(yōu)先取error.message來作為返回的message內(nèi)容轴总,如果沒有的話才會(huì)使用Exception中的message信息

解決方案二:ErrorFilter處理

通過上面的分析與實(shí)驗(yàn),我們已經(jīng)知道如何在過濾器中正確的處理異常博个,讓錯(cuò)誤信息能夠順利地流轉(zhuǎn)到后續(xù)的SendErrorFilter過濾器來組織和輸出肘习。但是,即使我們不斷強(qiáng)調(diào)要在過濾器中使用try-catch來處理業(yè)務(wù)邏輯并往請(qǐng)求上下文添加異常信息坡倔,但是不可控的人為因素漂佩、意料之外的程序因素等,依然會(huì)使得一些異常從過濾器中拋出罪塔,對(duì)于意外拋出的異常又會(huì)導(dǎo)致沒有控制臺(tái)輸出也沒有任何響應(yīng)信息的情況出現(xiàn)投蝉,那么是否有什么好的方法來為這些異常做一個(gè)統(tǒng)一的處理呢?

這個(gè)時(shí)候征堪,我們就可以用到error類型的過濾器了瘩缆。由于在請(qǐng)求生命周期的preroute佃蚜、post三個(gè)階段中有異常拋出的時(shí)候都會(huì)進(jìn)入error階段的處理庸娱,所以我們可以通過創(chuàng)建一個(gè)error類型的過濾器來捕獲這些異常信息着绊,并根據(jù)這些異常信息在請(qǐng)求上下文中注入需要返回給客戶端的錯(cuò)誤描述,這里我們可以直接沿用在try-catch處理異常信息時(shí)用的那些error參數(shù)熟尉,這樣就可以讓這些信息被SendErrorFilter捕獲并組織成消息響應(yīng)返回給客戶端归露。比如,下面的代碼就實(shí)現(xiàn)了這里所描述的一個(gè)過濾器:

public class ErrorFilter extends ZuulFilter {

    Logger log = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 10;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        Throwable throwable = ctx.getThrowable();
        log.error("this is a ErrorFilter : {}", throwable.getCause().getMessage());
        ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        ctx.set("error.exception", throwable.getCause());
        return null;
    }

}

在將該過濾器加入到我們的API網(wǎng)關(guān)服務(wù)之后斤儿,我們可以嘗試使用之前介紹try-catch處理時(shí)實(shí)現(xiàn)的ThrowExceptionFilter(不包含異常處理機(jī)制的代碼)剧包,讓該過濾器能夠拋出異常。這個(gè)時(shí)候我們?cè)偻ㄟ^API網(wǎng)關(guān)來訪問服務(wù)接口往果。此時(shí)疆液,我們就可以在控制臺(tái)中看到ThrowExceptionFilter過濾器拋出的異常信息,并且請(qǐng)求響應(yīng)中也能獲得如下的錯(cuò)誤信息內(nèi)容陕贮,而不是什么信息都沒有的情況了堕油。

{
  "timestamp": 1481674993561,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "java.lang.RuntimeException",
  "message": "Exist some errors..."
}

本文節(jié)選自《Spring Cloud微服務(wù)實(shí)戰(zhàn)》,部分稍做加工肮之,轉(zhuǎn)載請(qǐng)注明出處

本文由 程序猿DD-翟永超 創(chuàng)作掉缺,采用 CC BY 3.0 CN協(xié)議 進(jìn)行許可。 可自由轉(zhuǎn)載局骤、引用攀圈,但需署名作者且注明文章出處暴凑。
原文首發(fā)于:http://blog.didispace.com/spring-cloud-zuul-exception/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峦甩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子现喳,更是在濱河造成了極大的恐慌凯傲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗦篱,死亡現(xiàn)場(chǎng)離奇詭異冰单,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)灸促,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門诫欠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浴栽,你說我怎么就攤上這事荒叼。” “怎么了典鸡?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵被廓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我萝玷,道長(zhǎng)嫁乘,這世上最難降的妖魔是什么昆婿? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蜓斧,結(jié)果婚禮上仓蛆,老公的妹妹穿的比我還像新娘。我一直安慰自己法精,他們只是感情好多律,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搂蜓,像睡著了一般狼荞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帮碰,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天相味,我揣著相機(jī)與錄音,去河邊找鬼殉挽。 笑死丰涉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斯碌。 我是一名探鬼主播一死,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼傻唾!你這毒婦竟也來了投慈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤冠骄,失蹤者是張志新(化名)和其女友劉穎伪煤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凛辣,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抱既,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扁誓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片防泵。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝗敢,靈堂內(nèi)的尸體忽然破棺而出捷泞,到底是詐尸還是另有隱情,我是刑警寧澤前普,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布肚邢,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏骡湖。R本人自食惡果不足惜贱纠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望响蕴。 院中可真熱鬧谆焊,春花似錦、人聲如沸浦夷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)劈狐。三九已至罐孝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肥缔,已是汗流浹背莲兢。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留续膳,地道東北人改艇。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坟岔,于是被迫代替她去往敵國(guó)和親谒兄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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