从源码角度理解Java设计模式——责任链模式

本文内容思维导图如下:

一、责任链模式介绍

责任链模式定义:为请求创建一个处理此请求对象的链。

适用场景(核心):只要把你的请求抛给第一个处理者,不用关心谁处理的,并且最终会返回你一个结果。

优点:请求者和处理者解耦,请求者不用知道谁处理的,处理者可以不用知道请求的全貌。

缺点:每个请求从链头遍历到链尾,影响性能。代码调试时候不方便。

类型:行为型。

类图:

源码中的典型应用:

  1. Netty 中的 Pipeline和ChannelHandler通过责任链设计模式来组织代码逻辑。
  2. Spring Security 使用责任链模式,可以动态地添加或删除责任(处理 request 请求)。ref:SPRING与设计模式---责任链模式
  3. Spring AOP 通过责任链模式来管理 Advisor。
  4. Dubbo Filter 过滤器链也是用了责任链模式(链表),可以对方法调用做一些过滤处理,譬如超时(TimeoutFilter),异常(ExceptionFilter),Token(TokenFilter)等。
  5. Mybatis 中的 Plugin 机制使用了责任链模式,配置各种官方或者自定义的 Plugin,与 Filter 类似,可以在执行 Sql 语句的时候做一些操作。
  6. Tomcat 调用 ApplicationFilterFactory过滤器链。

二、请假示例

员工在OA系统中提交请假申请,首先项目经理处理,他能审批3天以内的假期,如果大于3天,则由项目经理则转交给总经理处理。接下来我们用责任链模式实现这个过程。

1、封装请假信息实体类

public class LeaveRequest {
    private String name;    // 请假人姓名
    private int numOfDays;  // 请假天数
    private int workingAge;  //员工工龄(在公司大于2年则总经理会审批)
   //省略get..set..
}

2、抽象处理者类 Handler,维护一个nextHandler属性,该属性为当前处理者的下一个处理者的引用;声明了抽象方法process,其实在这里也用了方法模板模式:

public abstract class ApproveHandler {

    protected  ApproveHandler nextHandler;//下一个处理者(与类一致,这段代码很重要)

    public void setNextHandler(ApproveHandler approveHandler){
        this.nextHandler=approveHandler;
    }

    public abstract void process(LeaveRequest leaveRequest); // 处理请假(这里用了模板方法模式)

}

3、项目经理处理者,能处理小于3天的假期,而请假信息里没有名字时,审批不通过:

public class PMHandler extends ApproveHandler{

    @Override
    public void process(LeaveRequest leaveRequest) {
        //未填写姓名的请假单不通过
        if(null != leaveRequest.getName()){
            if(leaveRequest.getNumOfDays() <= 3){
                System.out.println(leaveRequest.getName()+",你通过项目经理审批!");
            }else {
                System.out.println("项目经理转交总经理");
                if(null != nextHandler){
                    nextHandler.process(leaveRequest);
                }
            }
        }else {
            System.out.println("请假单未填写完整,未通过项目经理审批!");
            return;
        }
    }
}

4、总经理处理者,能处理大于3天的假期,且工龄超过2年才会审批通过:

public class GMHandler extends ApproveHandler{

    @Override
    public void process(LeaveRequest leaveRequest) {
        //员工在公司工龄超过2年,则审批通过
        if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){
            System.out.println(leaveRequest.getName()+",你通过总经理审批!");
            if(null != nextHandler){
                nextHandler.process(leaveRequest);
            }
        }else {
            System.out.println("在公司年限不够,长假未通过总经理审批!");
            return;
        }
    }
}

实例代码完成,我们测试一下:

public class Test {
    public static void main(String[] args) {
        PMHandler pm = new PMHandler();
        GMHandler gm = new GMHandler();

        LeaveRequest leaveRequest = new LeaveRequest();
        leaveRequest.setName("张三");
        leaveRequest.setNumOfDays(4);//请假4天
        leaveRequest.setWorkingAge(3);//工龄3年

        pm.setNextHandler(gm);//设置传递顺序
        pm.process(leaveRequest);
    }
}

运行结果:

------

项目经理转交总经理 张三,你通过总经理审批!

------

三、源码中的责任链模式

Filter接口有非常多的实现类,这里挑选doFilter方法中的FilterChain参数来看,Tomcat和SpringSecurity中都用到责任链模式:

进入第一个,过滤器链 ApplicationFilterChain 的关键代码如下,过滤器链实际是一个 ApplicationFilterConfig 数组:

final class ApplicationFilterChain implements FilterChain, CometFilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 过滤器链
    private Servlet servlet = null; // 目标
    // ...

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            // ...
        } else {
            internalDoFilter(request,response); // 调用 internalDoFilter 方法
        }
    }

    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            // 从过滤器数组中取出当前过滤器配置,然后下标自增1
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();  // 从过滤器配置中取出该 过滤器对象

                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    // 调用过滤器的 doFilter,完成一个过滤器的过滤功能
                    filter.doFilter(request, response, this);
                }
            return;  // 这里很重要,不会重复执行后面的  servlet.service(request, response)
        }

        // 执行完过滤器链的所有过滤器之后,调用 Servlet 的 service 完成请求的处理
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
            if( Globals.IS_SECURITY_ENABLED ) {

            } else {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
    }
    // 省略...
}

这里可以看出ApplicationFilterChain类扮演了抽象处理者角色,doFilter就类似于刚才请假流程里的process方法。

当下标小于过滤器数组长度 n 时,也就是过滤器链未执行完,从数组中取出并调用当前过滤器的 doFilter方法 ,如果下标一直小于n,则循环调用doFilter方法通过嵌套递归的方式来串成一条链。

当最后的过滤器执行完毕,也就是走到最后一个return;时,结束递归调用doFilter。if (pos < n) 为false,调用后面的servlet.service(request, response) 方法。return;这一点在请假流程里也有体现。

参考:

设计模式 | 责任链模式及典型应用

责任链设计模式(过滤器、拦截器)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券