新聞中心
在前幾天發(fā)布的《Spring Cloud實(shí)戰(zhàn)小貼士:Zuul統(tǒng)一異常處理(一)》一文中,我們詳細(xì)說明了當(dāng)Zuul的過濾器中拋出異常時會發(fā)生客戶端沒有返回任何內(nèi)容的問題以及針對這個問題的兩種解決方案:一種是通過在各個階段的過濾器中增加try-catch塊,實(shí)現(xiàn)過濾器內(nèi)部的異常處理;另一種是利用error類型過濾器的生命周期特性,集中地處理pre、route、post階段拋出的異常信息。通常情況下,我們可以將這兩種手段同時使用,其中***種是對開發(fā)人員的基本要求;而第二種是對***種處理方式的補(bǔ)充,以防止一些意外情況的發(fā)生。這樣的異常處理機(jī)制看似已經(jīng)***,但是如果在多一些應(yīng)用實(shí)踐或源碼分析之后,我們會發(fā)現(xiàn)依然存在一些不足。

創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比陽東網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式陽東網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋陽東地區(qū)。費(fèi)用合理售后完善,十載實(shí)體公司更值得信賴。
不足之處
下面,我們不妨跟著源碼來看看,到底上面的方案還有哪些不足之處需要我們注意和進(jìn)一步優(yōu)化的。先來看看外部請求到達(dá)API網(wǎng)關(guān)服務(wù)之后,各個階段的過濾器是如何進(jìn)行調(diào)度的:
- try {
- preRoute();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- route();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- postRoute();
- } catch (ZuulException e) {
- error(e);
- return;
- }
上面代碼源自com.netflix.zuul.http.ZuulServlet的service方法實(shí)現(xiàn),它定義了Zuul處理外部請求過程時,各個類型過濾器的執(zhí)行邏輯。從代碼中我們可以看到三個try-catch塊,它們依次分別代表了pre、route、post三個階段的過濾器調(diào)用,在catch的異常處理中我們可以看到它們都會被error類型的過濾器進(jìn)行處理(之前使用error過濾器來定義統(tǒng)一的異常處理也正是利用了這個特性);error類型的過濾器處理完畢之后,除了來自post階段的異常之外,都會再被post過濾器進(jìn)行處理。而對于從post過濾器中拋出異常的情況,在經(jīng)過了error過濾器處理之后,就沒有其他類型的過濾器來接手了,這就是使用之前所述方案存在不足之處的根源。
分析與優(yōu)化
回想一下之前實(shí)現(xiàn)的兩種異常處理方法,其中非常核心的一點(diǎn),這兩種處理方法都在異常處理時候往請求上下文中添加了一系列的error.*參數(shù),而這些參數(shù)真正起作用的地方是在post階段的SendErrorFilter,在該過濾器中會使用這些參數(shù)來組織內(nèi)容返回給客戶端。而對于post階段拋出異常的情況下,由error過濾器處理之后并不會在調(diào)用post階段的請求,自然這些error.*參數(shù)也就不會被SendErrorFilter消費(fèi)輸出。所以,如果我們在自定義post過濾器的時候,沒有正確的處理異常,就依然有可能出現(xiàn)日志中沒有異常并且請求響應(yīng)內(nèi)容為空的問題。我們可以通過修改之前ThrowExceptionFilter的filterType修改為post來驗(yàn)證這個問題的存在,注意去掉try-catch塊的處理,讓它能夠拋出異常。
解決上述問題的方法有很多種,比如最直接的我們可以在實(shí)現(xiàn)error過濾器的時候,直接來組織結(jié)果返回就能實(shí)現(xiàn)效果,但是這樣的缺點(diǎn)也很明顯,對于錯誤信息組織和返回的代碼實(shí)現(xiàn)就會存在多份,這樣非常不易于我們?nèi)蘸蟮拇a維護(hù)工作。所以為了保持對異常返回處理邏輯的一致,我們還是希望將post過濾器拋出的異常能夠交給SendErrorFilter來處理。
在前文中,我們已經(jīng)實(shí)現(xiàn)了一個ErrorFilter來捕獲pre、route、post過濾器拋出的異常,并組織error.*參數(shù)保存到請求的上下文中。由于我們的目標(biāo)是沿用SendErrorFilter,這些error.*參數(shù)依然對我們有用,所以我們可以繼續(xù)沿用該過濾器,讓它在post過濾器拋出異常的時候,繼續(xù)組織error.*參數(shù),只是這里我們已經(jīng)無法將這些error.*參數(shù)再傳遞給SendErrorFitler過濾器來處理了。所以,我們需要在ErrorFilter過濾器之后再定義一個error類型的過濾器,讓它來實(shí)現(xiàn)SendErrorFilter的功能,但是這個error過濾器并不需要處理所有出現(xiàn)異常的情況,它僅對post過濾器拋出的異常才有效。根據(jù)上面的思路,我們完全可以創(chuàng)建一個繼承自SendErrorFilter的過濾器,就能復(fù)用它的run方法,然后重寫它的類型、順序以及執(zhí)行條件,實(shí)現(xiàn)對原有邏輯的復(fù)用,具體實(shí)現(xiàn)如下:
- try {
- preRoute();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- route();
- } catch (ZuulException e) {
- error(e);
- postRoute();
- return;
- }
- try {
- postRoute();
- } catch (ZuulException e) {
- error(e);
- return;
- }
到這里,我們在過濾器調(diào)度上的實(shí)現(xiàn)思路已經(jīng)很清晰了,但是又有一個問題出現(xiàn)在我們面前:怎么判斷引起異常的過濾器是來自什么階段呢?(shouldFilter方法該如何實(shí)現(xiàn))對于這個問題,我們***反應(yīng)會寄希望于請求上下文RequestContext對象,可是在查閱文檔和源碼后發(fā)現(xiàn)其中并沒有存儲異常來源的內(nèi)容,所以我們不得不擴(kuò)展原來的過濾器處理邏輯,當(dāng)有異常拋出的時候,記錄下拋出異常的過濾器,這樣我們就可以在ErrorExtFilter過濾器的shouldFilter方法中獲取并以此判斷異常是否來自post階段的過濾器了。
為了擴(kuò)展過濾器的處理邏輯,為請求上下文增加一些自定義屬性,我們需要深入了解一下Zuul過濾器的核心處理器:com.netflix.zuul.FilterProcessor。該類中定義了下面過濾器調(diào)用和處理相關(guān)的核心方法:
- getInstance():該方法用來獲取當(dāng)前處理器的實(shí)例
- setProcessor(FilterProcessor processor):該方法用來設(shè)置處理器實(shí)例,可以使用此方法來設(shè)置自定義的處理器
- processZuulFilter(ZuulFilter filter):該方法定義了用來執(zhí)行filter的具體邏輯,包括對請求上下文的設(shè)置,判斷是否應(yīng)該執(zhí)行,執(zhí)行時一些異常處理等
- getFiltersByType(String filterType):該方法用來根據(jù)傳入的filterType獲取API網(wǎng)關(guān)中對應(yīng)類型的過濾器,并根據(jù)這些過濾器的filterOrder從小到大排序,組織成一個列表返回
- runFilters(String sType):該方法會根據(jù)傳入的filterType來調(diào)用getFiltersByType(String filterType)獲取排序后的過濾器列表,然后輪詢這些過濾器,并調(diào)用processZuulFilter(ZuulFilter filter)來依次執(zhí)行它們
- preRoute():調(diào)用runFilters("pre")來執(zhí)行所有pre類型的過濾器
- route():調(diào)用runFilters("route")來執(zhí)行所有route類型的過濾器
- postRoute():調(diào)用runFilters("post")來執(zhí)行所有post類型的過濾器
- error():調(diào)用runFilters("error")來執(zhí)行所有error類型的過濾器
根據(jù)我們之前的設(shè)計(jì),我們可以直接通過擴(kuò)展processZuulFilter(ZuulFilter filter)方法,當(dāng)過濾器執(zhí)行拋出異常的時候,我們捕獲它,并往請求上下中記錄一些信息。比如下面的具體實(shí)現(xiàn):
- public class DidiFilterProcessor extends FilterProcessor {
- @Override
- public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
- try {
- return super.processZuulFilter(filter);
- } catch (ZuulException e) {
- RequestContext ctx = RequestContext.getCurrentContext();
- ctx.set("failed.exception", e);
- ctx.set("failed.filter", filter);
- throw e;
- }
- }
- }
在上面代碼的實(shí)現(xiàn)中,我們創(chuàng)建了一個FilterProcessor的子類,并重寫了processZuulFilter(ZuulFilter filter),雖然主邏輯依然使用了父類的實(shí)現(xiàn),但是在最外層,我們?yōu)槠湓黾恿水惓2东@,并在異常處理中為請求上下文添加了failed.filter屬性,以存儲拋出異常的過濾器實(shí)例。在實(shí)現(xiàn)了這個擴(kuò)展之后,我們也就可以完善之前ErrorExtFilter中的shouldFilter()方法,通過從請求上下文中獲取該信息作出正確的判斷,具體實(shí)現(xiàn)如下:
- public class ErrorExtFilter extends SendErrorFilter {
- @Override
- public String filterType() {
- return "error";
- }
- @Override
- public int filterOrder() {
- return 30;
- }
- @Override
- public boolean shouldFilter() {
- RequestContext ctx = RequestContext.getCurrentContext();
- ZuulFilter failedFilter = (ZuulFilter) ctx.get("failed.filter");
- if(failedFilter != null && failedFilter.filterType().equals("post")) {
- return true;
- }
- return false;
- }
- }
到這里,我們的優(yōu)化任務(wù)還沒有完成,因?yàn)閿U(kuò)展的過濾器處理類并還沒有生效。***,我們需要在應(yīng)用主類中,通過調(diào)用FilterProcessor.setProcessor(new DidiFilterProcessor());方法來啟用自定義的核心處理器以完成我們的優(yōu)化目標(biāo)。
【本文為專欄作者“翟永超”的原創(chuàng)稿件,轉(zhuǎn)載請通過聯(lián)系作者獲取授權(quán)】
戳這里,看該作者更多好文
網(wǎng)頁標(biāo)題:Spring Cloud實(shí)戰(zhàn)小貼士:Zuul統(tǒng)一異常處理(二)
文章起源:http://www.dlmjj.cn/article/djsosjs.html


咨詢
建站咨詢
