通过对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