Spring Cloud Zuul 分析(三)之ZuulFilter調(diào)用過程

ZuulFilter的兩種初始化過程我們在前面已經(jīng)分析過柱徙,這一節(jié)我們也直奔主題缓屠,講講ZuulFilter初始化之后的調(diào)用過程,看看整個(gè)調(diào)用過程中Zuul是如何處理的护侮,都經(jīng)過了哪些步驟敌完?下面我們就以routing filters執(zhí)行過程進(jìn)行分析!


ZuulServletFilter

public class ZuulServletFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            ......
            try {
                routing();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }
            ......
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
    //routing filters執(zhí)行階段羊初,請求下游服務(wù)在此階段執(zhí)行
    void routing() throws ZuulException {
        zuulRunner.route();
    }
}

ZuulRunner

public class ZuulRunner {
    ......
    //初始化滨溉,默認(rèn)直接使用HttpServletRequest輸入流,輸出則使用HttpServletResponseWrapper包裝流
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }
        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }
    ......
    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }
    ......
}

ZuulRunner這個(gè)對象類中我們看見了有HttpServletRequest长赞、HttpServletResponse晦攒、HttpServletRequestWrapper、HttpServletResponseWrapper四種類型
HttpServletRequest中參數(shù)是無法修改的得哆,HttpServletResponse中輸出流是無法讀取的脯颜,但是往往我們有很多需求是需要進(jìn)行請求參數(shù)的修改及響應(yīng)輸出流讀取的,這個(gè)時(shí)候就需要我們使用HttpServletRequestWrapper贩据、HttpServletResponseWrapper這兩個(gè)包裝類栋操,
HttpServletRequestWrapper作為HttpServletRequest的包裝類,主要職責(zé)就是可以替換HttpServletRequest中的參數(shù)
HttpServletResponseWrapper作為HttpServletResponse的包裝類饱亮,主要職責(zé)就是可以讀取HttpServletResponse中的輸出流矾芙!


FilterProcessor#route()

public class FilterProcessor {
    ......
    public void route() throws ZuulException {
        try {
            runFilters("route");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
        }
    }
    ......
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        //根據(jù)filter類型獲取所有相關(guān)類型的ZuulFilter
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                //執(zhí)行ZuulFilter#runFilter()調(diào)用IZuulFilter#run()方法
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }
    ......
}

從片段中我們也比較清晰的得知調(diào)用過程為:
1.根據(jù)filter類型獲取所有相關(guān)類型的ZuulFilter
2.執(zhí)行ZuulFilter的run()方法
那么我們看看如何獲取List<ZuulFilter>這個(gè)集合對象的。


FilterLoader#getFiltersByType()

public class FilterLoader {
    ......
    public List<ZuulFilter> getFiltersByType(String filterType) {
        //如果hashFiltersByType緩存對象中已經(jīng)有當(dāng)前filterType類型的ZuulFilter集合則使用緩存
        List<ZuulFilter> list = hashFiltersByType.get(filterType);
        if (list != null) return list;
        list = new ArrayList<ZuulFilter>();
        //獲取全局對象filterRegistry中保存的所有ZuulFilter
        Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
        //添加到list對象中
        for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
            ZuulFilter filter = iterator.next();
            if (filter.filterType().equals(filterType)) {
                list.add(filter);
            }
        }
        //根據(jù)filterOrder優(yōu)先級排序近上,默認(rèn)從小到大
        Collections.sort(list);
        //添加到hashFiltersByType對象中
        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }
    ......
}

通過filterType類型獲取對應(yīng)的List<ZuulFilter>集合對象剔宪,其中筆者剛開始以為List<ZuulFilter> list = hashFiltersByType.get(filterType);這個(gè)地方有問題,因?yàn)閱为?dú)看這個(gè)地方會(huì)有一個(gè)問題,就是這個(gè)地方獲取出來如果有數(shù)據(jù)歼跟,那么就直接返回了和媳,那比如我動(dòng)態(tài)增加了一個(gè)Groovy文件并且類型相同的ZuulFilter,那這個(gè)地方看起來好想是有問題的哈街。
所以帶著疑問留瞳,我去仔細(xì)看了看初始化Groovy文件并初始化為ZuulFilter對象后添加到全局filterRegistry的過程,在FilterLoader#putFilter()這個(gè)方法中骚秦,有兩行非常重要的代碼她倘,List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
hashFiltersByType.remove(filter.filterType());這里如果有相同類型的,那么就直接就刪除了filterType對應(yīng)的List<ZuulFilter>集合數(shù)據(jù)作箍,在后面重新賦值硬梁,所以疑問迎刃而解!


FilterProcessor#processZuulFilter()

public class FilterProcessor {
    ......
    //執(zhí)行ZuulFilter的run()邏輯
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        ......
        try {
            ......
            //重點(diǎn):執(zhí)行ZuulFilter#runFilter()內(nèi)部會(huì)調(diào)用run()方法
            ZuulFilterResult result = filter.runFilter();
            ExecutionStatus s = result.getStatus();
            execTime = System.currentTimeMillis() - ltime;
            //失敗胞得、成功的邏輯
            switch (s) {
                case FAILED:
                    t = result.getException();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                    break;
                case SUCCESS:
                    o = result.getResult();
                    ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                    if (bDebug) {
                        Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
                        Debug.compareContextState(filterName, copy);
                    }
                    break;
                default:
                    break;
            }
            if (t != null) throw t;
            usageNotifier.notify(filter, s);
            return o;
        } catch (Throwable e) {
           ......
        }
    }
    ......
}

在processZuulFilter這個(gè)方法體中荧止,會(huì)記錄每個(gè)ZuulFilter的耗時(shí)時(shí)間,以及執(zhí)行過程中的成功阶剑、失敗跃巡、異常的狀態(tài)都會(huì)通知給Netflix Servo 實(shí)現(xiàn)監(jiān)控信息采集(JMX -Java Management Extensions標(biāo)準(zhǔn)),監(jiān)控信息不過多分析(PS:開啟JMX牧愁,然后使用JConsole或Visual VM進(jìn)行預(yù)覽)


ZuulFilter#runFilter()

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
    ......
    public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        //默認(rèn)false素邪,及所有ZuulFilter都有效
        if (!isFilterDisabled()) {
            //是否滿足過濾條件
            if (shouldFilter()) {
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
                    //重點(diǎn):實(shí)現(xiàn)了ZuulFilter都會(huì)重寫這個(gè)方法,執(zhí)行具體的業(yè)務(wù)邏輯方法
                    Object res = run();
                    zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                } catch (Throwable e) {
                    t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                    zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                    zr.setException(e);
                } finally {
                    t.stopAndLog();
                }
            } else {
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }
        return zr;
    }
    ......
}

分析到這里猪半,執(zhí)行run()方法兔朦,因?yàn)槲覀兪欠治鰎oute 類型的filterType,并且是調(diào)用下游服務(wù)的ZuulFilter磨确,所以我們最終會(huì)執(zhí)行到RibbonRoutingFilter這個(gè)實(shí)現(xiàn)類沽甥,那這個(gè)實(shí)現(xiàn)類都做了什么事情呢?我們接著往下俐填!


RibbonRoutingFilter

public class RibbonRoutingFilter extends ZuulFilter {
    ......
    //配置的routes路由為使用serviceId方式安接,非url配置方式
    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
                && ctx.sendZuulResponse());
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        //添加忽略的頭信息,zuul的ignored-headers配置,會(huì)過濾這些頭信息參數(shù)
        this.helper.addIgnoredHeaders();
        try {
            //構(gòu)建Ribbon命令執(zhí)行的上下文,保存相關(guān)參數(shù)的對象
            RibbonCommandContext commandContext = buildCommandContext(context);
            //調(diào)用下游服務(wù)英融,返回結(jié)果
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            throw new ZuulRuntimeException(ex);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
    }
    ......
}

這里我們簡單解釋下在run()方法中經(jīng)過的步驟RibbonRoutingFilter(routing filters)->Hystrix->Ribbon-> Response盏檐。如果想了解routing filters->Hystrix->Ribbon-> Response整個(gè)詳細(xì)過程請參閱:Spring Cloud Hystrix 分析(四)之Zuul集成

  1. 構(gòu)建Ribbon命令執(zhí)行的上下文,保存相關(guān)參數(shù)的對象驶悟,封裝RibbonCommandContext對象(頭信息胡野、請求參數(shù)信息)
  2. 封裝HttpClientRibbonCommand(RibbonCommand)對象,設(shè)置負(fù)載均衡客戶端痕鳍,而且內(nèi)部繼承HystrixExecutable硫豆,可以使用Hystrix熔斷功能
  3. 執(zhí)行命令龙巨,最終調(diào)用到HystrixCommand#execute()
  4. AbstractRibbonCommand#run()方法中this.client.executeWithLoadBalancer(request, config);通過負(fù)載均衡客戶端進(jìn)行下游服務(wù)的調(diào)用并返回結(jié)果

文章到這里也接近尾聲,本節(jié)主要講解了route類型ZuulFilter的整個(gè)調(diào)用過程熊响,其他類型的也基本差不多的旨别,都是相同類型的ZuulFilter按照從小到大的優(yōu)先級進(jìn)行執(zhí)行,如果文章對你有所幫助汗茄,就點(diǎn)贊關(guān)注吧秸弛!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市洪碳,隨后出現(xiàn)的幾起案子递览,更是在濱河造成了極大的恐慌,老刑警劉巖瞳腌,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绞铃,死亡現(xiàn)場離奇詭異,居然都是意外死亡嫂侍,警方通過查閱死者的電腦和手機(jī)儿捧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吵冒,“玉大人纯命,你說我怎么就攤上這事”云埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵瞭空,是天一觀的道長揪阿。 經(jīng)常有香客問我,道長咆畏,這世上最難降的妖魔是什么南捂? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮旧找,結(jié)果婚禮上溺健,老公的妹妹穿的比我還像新娘。我一直安慰自己钮蛛,他們只是感情好鞭缭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魏颓,像睡著了一般岭辣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甸饱,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天沦童,我揣著相機(jī)與錄音仑濒,去河邊找鬼。 笑死偷遗,一個(gè)胖子當(dāng)著我的面吹牛墩瞳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播氏豌,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矗烛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箩溃?” 一聲冷哼從身側(cè)響起瞭吃,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涣旨,沒想到半個(gè)月后歪架,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霹陡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年和蚪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烹棉。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攒霹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浆洗,到底是詐尸還是另有隱情催束,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布伏社,位于F島的核電站抠刺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏摘昌。R本人自食惡果不足惜速妖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聪黎。 院中可真熱鬧罕容,春花似錦、人聲如沸稿饰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湘纵。三九已至脂崔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧喷,已是汗流浹背砌左。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工脖咐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汇歹。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓屁擅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親产弹。 傳聞我的和親對象是個(gè)殘疾皇子派歌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)痰哨,斷路器胶果,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 如果您@EnableZuulProxy您可以使用代理路徑上傳文件,只要文件很小斤斧,它就應(yīng)該工作早抠。對于大文件,有一個(gè)替...
    咔啡閱讀 930評論 0 1
  • 前提 最近在項(xiàng)目中使用了SpringCloud撬讽,基于zuul搭建了一個(gè)提供加解密蕊连、鑒權(quán)等功能的網(wǎng)關(guān)服務(wù)。鑒于之前沒...
    zhrowable閱讀 2,068評論 0 8
  • Zuul 的核心邏輯是由一系列緊密配合工作的 Filter 來實(shí)現(xiàn)的游昼,能夠在進(jìn)行 HTTP 請求或響應(yīng)的時(shí)候執(zhí)行相...
    laiyy0728閱讀 756評論 0 3
  • 注意:本文的前提是基于zuul的1.3.X版本來解析的甘苍,2.0版本采用了netty作為底層框架重新設(shè)計(jì)了整個(gè)zuu...
    wangxiaowu241閱讀 13,640評論 0 5