Spring Cloud Zuul 分析(三)之ZuulFilter调用过程

ZuulFilter的两种初始化过程我们在前面已经分析过,这一节我们也直奔主题,讲讲ZuulFilter初始化之后的调用过程,看看整个调用过程中Zuul是如何处理的,都经过了哪些步骤?下面我们就以routing filters执行过程进行分析!


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执行阶段,请求下游服务在此阶段执行
    void routing() throws ZuulException {
        zuulRunner.route();
    }
}

ZuulRunner

public class ZuulRunner {
    ......
    //初始化,默认直接使用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这个对象类中我们看见了有HttpServletRequest、HttpServletResponse、HttpServletRequestWrapper、HttpServletResponseWrapper四种类型
HttpServletRequest中参数是无法修改的,HttpServletResponse中输出流是无法读取的,但是往往我们有很多需求是需要进行请求参数的修改及响应输出流读取的,这个时候就需要我们使用HttpServletRequestWrapper、HttpServletResponseWrapper这两个包装类,
HttpServletRequestWrapper作为HttpServletRequest的包装类,主要职责就是可以替换HttpServletRequest中的参数
HttpServletResponseWrapper作为HttpServletResponse的包装类,主要职责就是可以读取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;
        //根据filter类型获取所有相关类型的ZuulFilter
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {
                ZuulFilter zuulFilter = list.get(i);
                //执行ZuulFilter#runFilter()调用IZuulFilter#run()方法
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        return bResult;
    }
    ......
}

从片段中我们也比较清晰的得知调用过程为:
1.根据filter类型获取所有相关类型的ZuulFilter
2.执行ZuulFilter的run()方法
那么我们看看如何获取List<ZuulFilter>这个集合对象的。


FilterLoader#getFiltersByType()

public class FilterLoader {
    ......
    public List<ZuulFilter> getFiltersByType(String filterType) {
        //如果hashFiltersByType缓存对象中已经有当前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);
            }
        }
        //根据filterOrder优先级排序,默认从小到大
        Collections.sort(list);
        //添加到hashFiltersByType对象中
        hashFiltersByType.putIfAbsent(filterType, list);
        return list;
    }
    ......
}

通过filterType类型获取对应的List<ZuulFilter>集合对象,其中笔者刚开始以为List<ZuulFilter> list = hashFiltersByType.get(filterType);这个地方有问题,因为单独看这个地方会有一个问题,就是这个地方获取出来如果有数据,那么就直接返回了,那比如我动态增加了一个Groovy文件并且类型相同的ZuulFilter,那这个地方看起来好想是有问题的。
所以带着疑问,我去仔细看了看初始化Groovy文件并初始化为ZuulFilter对象后添加到全局filterRegistry的过程,在FilterLoader#putFilter()这个方法中,有两行非常重要的代码,List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
hashFiltersByType.remove(filter.filterType());这里如果有相同类型的,那么就直接就删除了filterType对应的List<ZuulFilter>集合数据,在后面重新赋值,所以疑问迎刃而解!


FilterProcessor#processZuulFilter()

public class FilterProcessor {
    ......
    //执行ZuulFilter的run()逻辑
    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
        ......
        try {
            ......
            //重点:执行ZuulFilter#runFilter()内部会调用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这个方法体中,会记录每个ZuulFilter的耗时时间,以及执行过程中的成功、失败、异常的状态都会通知给Netflix Servo 实现监控信息采集(JMX -Java Management Extensions标准),监控信息不过多分析(PS:开启JMX,然后使用JConsole或Visual VM进行预览)


ZuulFilter#runFilter()

public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
    ......
    public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        //默认false,及所有ZuulFilter都有效
        if (!isFilterDisabled()) {
            //是否满足过滤条件
            if (shouldFilter()) {
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
                    //重点:实现了ZuulFilter都会重写这个方法,执行具体的业务逻辑方法
                    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;
    }
    ......
}

分析到这里,执行run()方法,因为我们是分析route 类型的filterType,并且是调用下游服务的ZuulFilter,所以我们最终会执行到RibbonRoutingFilter这个实现类,那这个实现类都做了什么事情呢?我们接着往下!


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配置,会过滤这些头信息参数
        this.helper.addIgnoredHeaders();
        try {
            //构建Ribbon命令执行的上下文,保存相关参数的对象
            RibbonCommandContext commandContext = buildCommandContext(context);
            //调用下游服务,返回结果
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            throw new ZuulRuntimeException(ex);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
    }
    ......
}

这里我们简单解释下在run()方法中经过的步骤RibbonRoutingFilter(routing filters)->Hystrix->Ribbon-> Response。如果想了解routing filters->Hystrix->Ribbon-> Response整个详细过程请参阅:Spring Cloud Hystrix 分析(四)之Zuul集成

  1. 构建Ribbon命令执行的上下文,保存相关参数的对象,封装RibbonCommandContext对象(头信息、请求参数信息)
  2. 封装HttpClientRibbonCommand(RibbonCommand)对象,设置负载均衡客户端,而且内部继承HystrixExecutable,可以使用Hystrix熔断功能
  3. 执行命令,最终调用到HystrixCommand#execute()
  4. AbstractRibbonCommand#run()方法中this.client.executeWithLoadBalancer(request, config);通过负载均衡客户端进行下游服务的调用并返回结果

文章到这里也接近尾声,本节主要讲解了route类型ZuulFilter的整个调用过程,其他类型的也基本差不多的,都是相同类型的ZuulFilter按照从小到大的优先级进行执行,如果文章对你有所帮助,就点赞关注吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容