从Tomcat中学习责任链模式

通过对Tomcat8.5.5版本的源码进行学习,发现Tomcat中有2个地方用到了责任链模式,一个是管道中的阀门,另一个是过滤器链中的过滤器。下面我们分别来看一下这两个责任链模式具体是如何使用的。

首先分析过滤器链中的过滤器:

注:以下示例代码摘自Tomcat8.5.5的源码并对其进行了简化,保留核心部分。

Filter接口如下所示:

public interface Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain);
}

Filter接口是所有具体过滤器必须实现的接口,该接口有一个核心的方法doFilter,方法对Request和Response进行处理,注意第三个参数类型为FilterChain,传入该参数的目的是为了责任能够传递到下一个过滤器。
具体Filter如下所示:

public class MyFilter implements Filter{
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) {
            //处理request和response
            //...
            chain.doFilter(request,response);
    }
}

首先根据需求对request和response进行处理,最后需要调用过滤器链中的doFilter方法以便责任能够传递到下一个过滤器。chain.doFilter(request,response),注意这个doFilter是FilterChain中的doFilter与Filter中的doFilter没有任何关系,名字虽然相同,但是参数是不一样的。FilterChain中的doFilter只有Request和Response两个参数,其实更好的命名方式应该是chain.doNextFilter(resquest,response)

那么,FilterChain是什么样的呢?我们上面已经看到了FilterChain的用法,也知道了FilterChain在调用doFilter(request,response)的时候需要有能力将任务传递到下一个Filter,因此我们可以推断,FilterChain中需要保存所有的Filter。在Tomcat的实现中,正是在FilterChain中用一个数组保存了所有的Filter,这不禁让我想起了java.util.ArrayList的实现。
在Tomcat中FilterChain也是一个接口,如下所示:

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response);
}

而ApplicationFilterChain实现了FilterChain,其核心代码如下所示:

final class ApplicationFilterChain implements FilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
    private int pos = 0;//维持过滤器链中的当前位置
    private int n = 0;//过滤器链中的过滤器数量
    public void doFilter(ServletRequest request, ServletResponse response) {
        internalDoFilter(request,response);
    }
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response) {
        if(posthis);
            return;
        }
    }
    void addFilter(ApplicationFilterConfig filterConfig) {
        //省略了扩容部分。。。
        filters[n++] = filterConfig;
    }
}

可以看出在FilterChain的实现中确实是用数组来保存所有的过滤器,但是并不是直接保存的Filter类型,而是保存的FilterConfig类型,可以把FilterConfig当做Filter的包装类,我们可以通过filterConfig.getFilter()拿到Filter实例。在internalDoFilter方法中我们看到了FilterChain是如何实现责任的传递的,通过pos和n这两个变量来判断有没有传递到过滤器链的最后面,如果没有到最后,则取出当前过滤器并调用filter.doFilter的方法,在前面MyFilter的实现当中,它的doFilter方法在最后又调用了chain.doFilter(),从而保证了过滤器链能够将责任传递到下一个过滤器中。pos和n这两个变量的作用又让我联想到了ArrayList中的迭代器的实现。

客户端代码如下所示:

public class Client{
    public static void main(String[] args) {
        Request request = new Request();
        Response response = new Response();
        Filter filter1 = new MyFilter1();
        Filter filter2 = new MyFilter2();
        Filter filter3 = new MyFilter3();
        FilterChain chain = new ApplicationFilterChain();
        ApplicationFilterConfig filterConfig1 = new ApplicationFilterConfig(filter1);
        ApplicationFilterConfig filterConfig2 = new ApplicationFilterConfig(filter2);
        ApplicationFilterConfig filterConfig3 = new ApplicationFilterConfig(filter3);
        chain.addFilter(filterConfig1);
        chain.addFilter(filterConfig2);
        chain.addFilter(filterConfig3);
        chain.doFilter(request, response);
    }
}

下面再分析管道中的阀门是如何应用责任链模式的

上面我们提到了FilterChain中用数组保存了所有的过滤器,而在管道Pipeline中则采用的是链表的方式将一个个的阀门Valve链接起来的,这让我想起了java.util.LinkedList的实现,Pipeline相当于LinkedList,Valve相当于LinkedList中的内部类Node,不过Valve并不是以内部类的形式存在。
Valve接口如下所示:

public interface Valve {
    public Valve getNext();
    public void setNext(Valve valve);
    public void invoke(Request request, Response response);
}

具体的阀门类如下所示:

public class MyValve implements Valve {
    protected Valve next = null;
    @Override
    public Valve getNext() {
        return next;
    }
    @Override
    public void setNext(Valve valve) {
        this.next = valve;
    }
    @Override
    public void invoke(Request request, Response response) {
        //处理request,response
        //...
        getNext().invoke(request, response);
        return;
    }
}

Pipeline接口如下所示:

public interface Pipeline{
    public Valve getBasic();
    public void setBasic(Valve valve);
    public void addValve(Valve valve);
    public Valve getFirst();
}

Tomcat中Pipeline的实现类为StandardPipeline,核心代码如下所示:

public class StandardPipeline implements Pipeline {
    protected Valve basic = null;
    protected Valve first = null;
    @Override
    public Valve getBasic() {
        return (this.basic);
    }
    @Override
    public void setBasic(Valve valve) {
        Valve oldBasic = this.basic;
        if (oldBasic == valve)
            return;
        if (valve == null)
            return;
        Valve current = first;
        while (current != null) {
            if (current.getNext() == oldBasic) {
                current.setNext(valve);
                break;
            }
            current = current.getNext();
        }
        this.basic = valve;
    }
    @Override
    public void addValve(Valve valve) {
        // Add this Valve to the set associated with this Pipeline
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }
    }
    @Override
    public Valve getFirst() {
        if (first != null) {
            return first;
        }
        return basic;
    }
}

可以看出StandardPipeline是一个具有头尾指针的单向链表。first相当于头指针,basic相当于尾指针。addValve方法的作用是把管道中的各个阀门链接起来。
在Tomcat中是通过容器组件对管道进行调用的,wrapper就是一种容器,因此可以通过如下方式进行调用:

wrapper.getPipeline().getFirst().invoke(request, response);

关于Tomcat的容器将会在其他的文章中进行分析。
从上面的调用方式可以看出,通过getPipeline()拿到管道对象,再调用管道对象的getFirst()拿到第一个阀门对象,再调用该阀门对象的invoke(request,response)方法,在该invoke方法的最后,如上面MyValve所示,会调用getNext().invoke(request, response)从而将责任传递到下一个阀门。

最后列出管道和过滤器链这两种责任链模式的对比:

管道/阀门 过滤器链/过滤器
管道(Pipeline) 过滤器链(FilterChain)
阀门(Valve) 过滤器(Filter)
底层实现为具有头(first)、尾(basic)指针的单向链表 底层实现为数组
Valve的核心方法invoke(request,response) Filter核心方法doFilter(request,response,chain)
pipeline.getFirst().invoke(request,response) filterchain.doFilter(request,response)

您的关注是我不断创作的动力源泉!期待认识更多的朋友,一起交流Java相关技术栈,共同进步!阅读更多技术文章,可关注我的公众号:codecrazy4j
这里写图片描述

你可能感兴趣的:(设计模式)