设计模式——责任链(结合Tomcat中Filter机制)

设计模式:责任链模式

说责任链之前,先引入一个场景,假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;以此为需求,写一个程序,你会怎么做?按着过程思维方式,最快最直白的就是,if else嘛,配合java,无非多追加学生类和各个角色的类。下面介绍的设计模式或许会给我们一些启发。

责任链模式

责任链又叫做职责链,是属于行为型设计模式,它的初衷是为了解决一个事件需要经过多个对象处理是很常见的场景。责任链的运作流程是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

责任链的实现

责任链的设计源于数据结构中的链表,从模式的定义中就能看出,它需要一串走下去,而每一个处理请求的对象,都需要记录下一个处理请求的对象,即标准的数据链表方式。

职责链模式的实现主要包含以下角色。

    1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
    2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
    3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

UML类图如下:

 

模式的实现例子:

参照以上的思想,我们针对一开始的场景编写请假的程序:

public class LeaveApprovalTest {
    public static void main(String[] args) {
        //组装责任链
        Leader teacher1 = new ClassAdviser();
        Leader teacher2 = new DepartmentHead();
        Leader teacher3 = new Dean();
        //Leader teacher4=new DeanOfStudies();
        teacher1.setNext(teacher2);
        teacher2.setNext(teacher3);
        //teacher3.setNext(teacher4);
        //提交请求
        teacher1.handleRequest(8);
    }
}
//抽象处理者:领导类
abstract class Leader {
    private Leader next;
    public void setNext(Leader next) {
        this.next = next;
    }
    public Leader getNext() {
        return next;
    }
    //处理请求的方法
    public abstract void handleRequest(int LeaveDays);
}
//具体处理者1:班主任类
class ClassAdviser extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 2) {
            System.out.println("班主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者2:系主任类
class DepartmentHead extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 7) {
            System.out.println("系主任批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者3:院长类
class Dean extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 10) {
            System.out.println("院长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}
//具体处理者4:教务处长类
class DeanOfStudies extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 20) {
            System.out.println("教务处长批准您请假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("请假天数太多,没有人批准该假条!");
            }
        }
    }
}

 

 

Tomcat中Filter的执行过程

 前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。

    • 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
    • 可动态指定一组对象处理请求,或添加新的处理者。
    • 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

说完了责任链的灵活应用,下面结合tomcat中Filter的例子,进行一个标准责任链的解析,先来看以下Tomcat的过滤器机制:

 这是一个tomcat处理请求的过程,即它会有多个过滤器,这里的过滤器串联起来,形成一条过滤链,前端或者浏览器发来了request,会经过这条链,顺着链依次经过每个过滤器,最终由servlet处理后,再逐一返回。这有点像栈结构,但是这其中逐一处理,构成一条链,又符合责任链的设计规则。

查看一下Tomcat中Filter接口的源码:

public interface Filter {
    void init(FilterConfig var1) throws ServletException;
    //熟悉的doFilter(), 熟悉的3个参数request, reponse, filterChain.
    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

下面是过滤链的接口源码:

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

  具体的过滤链的实现,都会带有一个容器,来存放该链中的Filter,即过滤链中包含一个个的过滤器。

做一个简化版的过滤机制

下面我们简化模拟一下Tomcat处理Filter的过程,

首先定义简易版的request和response对象

public class Request{
    String msg;
    public void setMsg(String msg){
         this.msg=msg;
   }
}


public class Response{

  public void deal(){
   System.out.println();
}
}

定义Filter接口及两个实现(http校验,消息敏感字符校验)

public interface Filter{
    void doFilter(Request req,Response rep,Filter filer);
}

public HttpFilter implements Filter{
    void doFilter(Request req,Response rep,Filter filer){
          System.out.println("处理了http验证"+req.getMsg());
          filter.doFilter(req,rep,filter);
   }
}

public SensitiveFilter implements Filter{
    void doFilter(Request req,Response rep,Filter filer){
          System.out.println("处理了敏感字符替换"+req.getMsg());
          filter.doFilter(req,rep,filter);
   }
}

定义过滤链:

public class FilterChain implements Filter{
  
    List<Filter> filterlist = new Arrary<>();
    private int index;  

    public FilterChain addFilter(Filter filter){
            filterlist.add(filter);
            return this;
    }

    void doFilter(Request req,Response res,Filter filter){
          if(index == filterlist.size()){
            return;//这里是逆序处理响应的关键, 当index为容器大小时, 证明对request的处理已经完成, 下面进入对response的处理.
        }
        Filter f = filterlist.get(index);//过滤器链按index的顺序拿到filter
        index++;
        f.doFilter(request, response, filter);
   }
}

测试代码:

public class DemoBox {
    public static void main(String[] args) {
        String msg = "大家好 ";//以下三行模拟一个请求
        Request request = new Request();
        request.setMsg(msg);

        Response response = new Response();//响应

        FilterChain fc = new FilterChain();//过滤器链
        HttpFilter f1 = new HttpFilter();//创建过滤器
        SensitiveFilter f2 = new SensitiveFilter();//创建过滤器
       
        fc.add(f1);//把过滤器添加到过滤器链中
        fc.add(f2);
        fc.doFilter(request, response, fc);//直接调用过滤器链的doFilter()方法进行处理

    }
}

   下面按着步骤,详细解释一下上面的代码

  • 首先我们分别创建一个RequestResponse对象. Request在传入进后端时需要依次被过滤器进行处理, Response对象在输出时要依次被过滤器处理.
  • 我们定义了一个Filter接口,它包含处理请求的方法doFilter,这里的Filter可以理解为责任链中的抽象处理者
  • 依次实现了两个拦截器,HttpFilter,SensitiveFilter,做具体的过滤处理,可以理解为责任链中具体处理者的角色
  • 实现一个Filter接口,做一个过滤链的类FilterChain,它除了基本的处理功能,还包含了一个过滤器容器FilterList,用它还存放整条链的Filter。
  • 接着我们调用过滤器链的doFilter()方法对request对象进行处理
  • 这时过滤器链中的index值为0, 通过index我们找到第一个过滤器并调用它的doFilter()方法
  • 进入doFilter()方法后, 首先会对request请求进行处理, 然后又调用了过滤器链的doFilter()方法. 这就是整个责任链模式的精妙之处, 它解释了为什么要给doFilter()加上一个过滤器链参数, 就是为了让每个过滤器可以调用过滤器链本身执行下一个过滤器。
  • 为什么要调用过滤器链本身? 因为当调用过滤器本身后, 程序将跳转回到过滤器链的doFilter方法执行, 这时index为1, 也就是拿到第二个过滤器, 然后继续处理。
  • 正是由于这个跳转, 使得过滤器中对response的处理暂时无法执行, 它必须等待上面的对过滤器链的方法返回才能被执行.