SpringCloud-源碼分析 zuul (二)

本文作者:陳剛籍琳,叩丁狼高級講師先舷。原創(chuàng)文章,轉(zhuǎn)載請注明出處前方。

在上一章節(jié)我們分析了Zuul中的各種filter跟继,那這一章我們來跟蹤一下zuul的執(zhí)行流程。那么入口肯定是我們的 ZuulServlet 镣丑,他類似于 DispatcherServlet 在請求的最前面做分發(fā)。我們來看一下他的源碼

/**
 * Core Zuul servlet which intializes and orchestrates zuulFilter execution
 *
 * @author Mikey Cohen
 *         Date: 12/23/11
 *         Time: 10:44 AM
 */
public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            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;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }

    /**
     * sets error context info and executes "error" filters
     *
     * @param e
     */
    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }

翻譯類上的注釋“Core Zuul servlet which intializes and orchestrates zuulFilter execution” 娱两, 他是最核心的servlet,負責(zé)調(diào)用各種filter去執(zhí)行莺匠,
在init初始化方法中獲取并使用 buffer-requests 配置來創(chuàng)建了ZuulRunner 對象。
在service方法中在依次調(diào)用 preRoute ,route ,postRoute ,如果出現(xiàn)異常會調(diào)用 error 十兢, 這這些方法最終都會去觸發(fā) zuulRunner中的各種路由方法趣竣。我們看一下ZuulRunner的源碼


/**
 * This class initializes servlet requests and responses into the RequestContext and wraps the FilterProcessor calls
 * to preRoute(), route(),  postRoute(), and error() methods
 *
 * @author mikey@netflix.com
 * @version 1.0
 */
public class ZuulRunner {

    private boolean bufferRequests;

    /**
     * Creates a new <code>ZuulRunner</code> instance.
     */
    public ZuulRunner() {
        this.bufferRequests = true;
    }

    /**
     *
     * @param bufferRequests - whether to wrap the ServletRequest in HttpServletRequestWrapper and buffer the body.
在ZuulServlet中創(chuàng)建ZuulRunner的時候傳入的配置參數(shù) bufferRequests, 它決定了是否把 ServletRequest包裝在HttpServletRequestWrapper中并緩沖主體旱物。
     */
    public ZuulRunner(boolean bufferRequests) {
        this.bufferRequests = bufferRequests;
    }

    /**
     * sets HttpServlet request and HttpResponse
     設(shè)置 請求對象和響應(yīng)對象到  RequestContext 中
     * @param servletRequest
     * @param servletResponse
     */
    public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
//把 ServletRequest包裝在HttpServletRequestWrapper中并緩沖主體遥缕。
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

//這里通過 FilterProcessor 執(zhí)行器調(diào)用各種 ZuulFilters
    /**
     * executes "post" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void postRoute() throws ZuulException {
        FilterProcessor.getInstance().postRoute();
    }

    /**
     * executes "route" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void route() throws ZuulException {
        FilterProcessor.getInstance().route();
    }

    /**
     * executes "pre" filterType  ZuulFilters
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        FilterProcessor.getInstance().preRoute();
    }

    /**
     * executes "error" filterType  ZuulFilters
     */
    public void error() {
        FilterProcessor.getInstance().error();
    }

從名字上我們能知道 ZuulRunner就是Zuul的執(zhí)行器 ,而Zuul中是通過各種filter來完成的宵呛,那么ZuulRunner就是負責(zé)去調(diào)用各種 filter 单匣。
從他的注釋上亦可得知 "This class initializes servlet requests and responses into the RequestContext and wraps the FilterProcessor calls to preRoute(), route(), postRoute(), and error() methods"大致意思是此類將servlet請求和響應(yīng)初始化為RequestContext,并使用FilterProcessor執(zhí)行器去調(diào)用preRoute()宝穗,route()户秤,postRoute()和error()方法,我們可以從這幾個方法中電源得到證實逮矛。

我們跟蹤一下 FilterProcessor的源碼看一下他是如何執(zhí)行的

public class FilterProcessor {
  ...省略代碼...
  /**
     * runs "post" filters which are called after "route" filters. ZuulExceptions from ZuulFilters are thrown.
     * Any other Throwables are caught and a ZuulException is thrown out with a 500 status code
     *
     * @throws ZuulException
     */
    public void postRoute() throws ZuulException {
        try {
            runFilters("post");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
        }
    }

    /**
     * runs all "error" filters. These are called only if an exception occurs. Exceptions from this are swallowed and logged so as not to bubble up.
     */
    public void error() {
        try {
            runFilters("error");
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * Runs all "route" filters. These filters route calls to an origin.
     *
     * @throws ZuulException if an exception occurs.
     */
    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());
        }
    }

    /**
     * runs all "pre" filters. These filters are run before routing to the orgin.
     *
     * @throws ZuulException
     */
    public void preRoute() throws ZuulException {
        try {
            runFilters("pre");
        } catch (ZuulException e) {
            throw e;
        } catch (Throwable e) {
            throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
        }
    }

    /**
     * runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
     *
     * @param sType the filterType.
     * @return
     * @throws Throwable throws up an arbitrary exception
     */
    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }
        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }

    /**
     * Processes an individual ZuulFilter. This method adds Debug information. Any uncaught Thowables are caught by this method and converted to a ZuulException with a 500 status code.
     *
     * @param filter
     * @return the return value for that filter
     * @throws ZuulException
     */
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        boolean bDebug = ctx.debugRouting();
        final String metricPrefix = "zuul.filter-";
        long execTime = 0;
        String filterName = "";
        try {
            long ltime = System.currentTimeMillis();
            filterName = filter.getClass().getSimpleName();
            
            RequestContext copy = null;
            Object o = null;
            Throwable t = null;

            if (bDebug) {
                Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
                copy = ctx.copy();
            }
            //真正執(zhí)行ZuulFilter內(nèi)存方法
            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) {
            if (bDebug) {
                Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
            }
            usageNotifier.notify(filter, ExecutionStatus.FAILED);
            if (e instanceof ZuulException) {
                throw (ZuulException) e;
            } else {
                ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                throw ex;
            }
        }
    }
...省略代碼...

我們先看 preRoute 鸡号, 他調(diào)用了 runFilters("pre")方法 ,"pre" 很明顯代表了filter的類型為前置過濾器须鼎,所有的“pre”過濾器調(diào)用都在這里面發(fā)起鲸伴, 而 runFilters 中通過 FilterLoader.getInstance().getFiltersByType(sType); 獲取了所有的“pre” 過濾器府蔗,然后遍歷通過調(diào)用Object result = processZuulFilter(zuulFilter); 方法執(zhí)行 并拿到結(jié)果 。

而在processZuulFilter方法中通過執(zhí)行 Filter 本身的 runFilter(); 方法完成調(diào)用 汞窗。

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
...省略...
 public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        if (!isFilterDisabled()) {
            if (shouldFilter()) {
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
              //真正執(zhí)行ZuulFilter內(nèi)存方法
                    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;
    }

這里最終調(diào)用了 IZuulFilter的run()方法 姓赤,那其實最終就調(diào)用到了 IZuulFilter的各種實現(xiàn)類filter的run方法,完成業(yè)務(wù)處理 杉辙。

到這里同學(xué)們只需要結(jié)合上一章節(jié)的每種filter的邏輯就能對zuul的執(zhí)行流程有一個大致的理解了模捂。

想獲取更多技術(shù)干貨,請前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜘矢,一起剝皮案震驚了整個濱河市狂男,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌品腹,老刑警劉巖岖食,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舞吭,居然都是意外死亡泡垃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門羡鸥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔑穴,“玉大人,你說我怎么就攤上這事惧浴〈婧停” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵衷旅,是天一觀的道長捐腿。 經(jīng)常有香客問我,道長柿顶,這世上最難降的妖魔是什么茄袖? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮嘁锯,結(jié)果婚禮上宪祥,老公的妹妹穿的比我還像新娘。我一直安慰自己家乘,他們只是感情好品山,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烤低,像睡著了一般肘交。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扑馁,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天涯呻,我揣著相機與錄音凉驻,去河邊找鬼。 笑死复罐,一個胖子當(dāng)著我的面吹牛涝登,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播效诅,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胀滚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乱投?” 一聲冷哼從身側(cè)響起咽笼,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戚炫,沒想到半個月后剑刑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡双肤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年施掏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茅糜。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡七芭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蔑赘,到底是詐尸還是另有隱情狸驳,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布米死,位于F島的核電站,受9級特大地震影響贮庞,放射性物質(zhì)發(fā)生泄漏峦筒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一窗慎、第九天 我趴在偏房一處隱蔽的房頂上張望物喷。 院中可真熱鬧,春花似錦遮斥、人聲如沸峦失。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尉辑。三九已至,卻和暖如春较屿,著一層夾襖步出監(jiān)牢的瞬間隧魄,已是汗流浹背卓练。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留购啄,地道東北人襟企。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像狮含,于是被迫代替她去往敵國和親顽悼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

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