Java的责任链模式

2年前 (2022) 程序员胖胖胖虎阿
178 0 0

1. 絮絮叨叨

学校的奖学金评选流程

  1. 辅导员审核:确认个人信息是否完善、真实
  2. 系主任审核:根据学生填写的信息,结合自己了解的情况,决定是否推荐该学生参与奖学金评选
  3. 院长审核:根据学生填写的信息,综合对比候选人,决定该生是否能获得奖学金
  4. 校长审核:一般,院长审核后的获奖人数都是学校规定的获奖人数,校长只需要同意即可;除非名单公示时,爆出了该生的黑料,取消该生的获奖资格 😂
  • 作为学生,只需要在学校的奖学金评选系统上,诚信地填写自己的信息即可
  • 至于,评选能走到哪一步,每一步由谁审批,都不是他关心的
  • 除非,他想搞事情 🤔

公司的请假系统

  1. 不超过3天的,组长审批
  2. 超过3天且小于7天的,总监审批
  3. 超过7天且小于15天的,部长审批
  4. 超过15天,前端直接拒绝,不会进入审批流程(违反了公司的请假规定)
  • 底层小职员请假,直接去OA系统填写请假申请,生成请假工单
  • 系统会根据请假天数,将请假工单派发给对应的审批人

来自菜鸟的感慨

  • 哇塞,这个奖学金评选系统好牛逼哦:幸运儿的申请,层层上报,最终会由校长进行审批

  • 哇塞,这个OA系统好智能啊:不同的请假天数,可以到对应审批人的手里

  • 要是我去实现这样一个系统,该怎么做呢?

  • 请假系统倒是好说,直接根据请假天数,来个if-else就可以指定审批人了

  • 但是,奖学金评选系统呢?难道写成这样?

    // 辅导员审批
    if (isTrue) {
        instructor.approve();
        // 系主任审批
        if (isOk) {
            departmentHead.approve();
            // 院长审批
            if (isGoodEnough) {
                dean.approve();
                // 校长审批
                if (isGood) {
                    headmaster.approve();
                } else {
                    System.out.println("很遗憾,未通过全校公示");
                }
            } else {
                System.out.println("很遗憾,未在学院评选中脱颖而出");
            }
        } else {
            System.out.println("该生表现欠佳,不予通过");
        }
    } else {
        System.out.println("信息填写有误,请重新认真填写");
    }
    
  • 我的天,这是什么代码啊,简直像坨屎 🤣

1.2 考虑使用责任链模式

  • 以请假审批系统为例,组长

    \rightarrow

    总监

    \rightarrow

    部长形成了一条链

  • 职员提交请假申请后,请求会沿着这条链传递,直到有对象可以处理它。
    • 一个5天的请假申请,先到达组长处;
    • 组长无权限审批,传递请求到自己的上级(总监);
    • 总监有权限审批,于是同意了该请假申请
  • 作为请求的发送者,职员无需关心最终由谁审批,只需要提交请求即可
  • 这样的设计模式,就是责任链模式(Chain of Responsibility)

2. 责任链模式

2.1 定义

责任链模式的定义

  • 为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;
  • 当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

敲黑板,划重点

重点1:多个请求处理者

  • 请求处理者至少应该有一个,不然请求无法被处理,也无法形成责任链

重点2:将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链

  • 根据请求的处理流程,请求处理者(记为handler)可以通过next引用形成一条链
  • 这条链,也就所谓的责任链

重点3:请求沿着这条链传递,直到有对象处理它为止

  • 如果请求能被当前handler处理,则先执行特定的处理
    • 根据处理流程,请求可能不再向next handler传递,结束整个处理流程,如请假审批
    • 也可能继续向next handler传递,直到到达责任链末尾,如奖学金评选
  • 如果请求不能被当前handler处理,则需要向next handler传递
  • 可能不存在对应的handler,导致请求无法被处理
  • 也就是说,定义中的描述可以改写为:请求沿着这条链传递,直到有handler处理它或者达到末尾为止

自己的理解

  • 一个请求可能需要由不同的handler独立处理,或者需要多个handler同时处理
  • 如果按照常规的编程模式,可能需要繁杂的if-else去实现
  • 将handler通过next引用形成一条责任链,请求发送者只需要发送请求到责任链
  • 请求会在责任链中传递,直到被对应的handler处理(一个或多个)或者无法被处理
  • 这样的话,请求发送者无需关心handler的处理逻辑和请求的传递过程,实现了请求的发送者和处理者的解耦

2.2 UML图

  • 下图,是基于一个具体的场景绘制出的责任链模式的UML图
    Java的责任链模式

  • 从UML图中,可以提炼出三个关键角色

    • 抽象处理者(Handler):一般为抽象类,定义一个处理请求的抽象方法,包含一个指向后继处理者的next引用,以构造责任链
    • 具体处理者(Concrete Handler):实现抽象处理者的处理方法,根据实际情况决定是否处理请求,是否传递请求给后继处理者
    • 客户类(Client):创建责任链,向责任链头部的具体处理者对象提交请求。客户类,不关心请求的处理细节和传递过程
  • 总结一下责任链模式

    • 抽象处理者:实现了构建责任链的逻辑(定义了next引用),创建了处理请求的抽象方法
    • 具体处理者:根据实际情况,实现请求处理方法
    • Client:负责创建责任链,并向责任链传递请求,从而达到根据特定流程处理请求的目的

2.3 实现一个请假系统

  • 按照博客开头的描述,实现一个请假系统

  • 第一步:创建抽象处理者

    public abstract class Handler {
        private Handler next;
    
        public void setNext(Handler next) {
            this.next = next;
        }
    
        public Handler getNext() {
            return next;
        }
    
        public abstract void handleRequest(String name, int days);
    }
    
  • 第二步:创建组长、总监、部长三个具体处理者,实现具体的处理逻辑

    public class PMHandler extends Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 3) {
                System.out.println(name + ",组长已经同意您的请假审批!");
            } else {
                if (getNext() != null) {
                    getNext().handleRequest(name, days);
                } else {
                    System.out.println("请假天数太多,申请被驳回!");
                }
            }
        }
    }
    
    public class DirectorHandler extends Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 7) {
                System.out.println(name + ",中心总监已经同意您的请假审批");
            } else {
                if (getNext() != null) {
                    getNext().handleRequest(name, days);
                } else {
                    System.out.println("请假天数太多,申请被驳回!");
                }
            }
        }
    }
    
    public class MinisterHandler extends Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 15) {
                System.out.println(name + ",部长已经同意您的请假审批");
            } else {
                if (getNext() != null) {
                    getNext().handleRequest(name, days);
                } else {
                    System.out.println("请假天数太多,申请被驳回!");
                }
            }
        }
    }
    
  • 第三步:创建Clinet类,在类中创建并使用责任链(向责任链传递请求)

    public class OASystem {
        public static void main(String[] args) {
            // 创建具体处理者
            Handler pm = new PMHandler();
            Handler director = new DirectorHandler();
            Handler minister = new MinisterHandler();
            // 构建责任链
            pm.setNext(director);
            director.setNext(minister);
    
            // 使用责任链
            pm.handleRequest("张三", 5);
        }
    }
    
  • 最终的执行结果如下:
    Java的责任链模式

2.4 请假系统的完善

需求一:

  • 对于超过3天且小于5天,需要由经理审批;然后才是总监审批
  • 这时,需要在客户类中,新增经理实例,修改组长实例的next,并设置经理实例的next

需求二:

  • 公司架构调整,超过3天的请假,需要直接由部长审批
  • 这时,需要在客户类中,去掉总监实例及其next设置,然后将组长实例的next设置为部长

现有代码存在的问题:

  • 对具体处理者的增加、删除或调整次序,都将涉及整个责任链的重构,容易引发错误
  • 说不定,脑壳一昏,就将总监设置为部长的后继处理者了

灵光一现

  • 在使用一些库函数的时候,经常看到类似的代码:

    Escapers.builder()
           .addEscape('"', "&quot;")
           .addEscape('\'', "&#39;")
           .addEscape('&', "&amp;")
           .addEscape('<', "&lt;")
           .addEscape('>', "&gt;")
           .build();
    
  • 这样的代码,增加、删除或者调整属性的顺序,都非常方便

较为成熟的责任链实现

  • 取消抽象处理者中的next引用

  • 单独的责任链类:

    • 提供添加具体处理者的add方法,基于List构建隐形的责任链;
    • 实现抽象处理者的请求处理方法,遍历责任链中的具体处理者,以实现请求的传递与处理
    // 抽象处理者
    public interface Handler {
        public abstract void handleRequest(String name, int days);
    }
    
    // 具体处理者
    public class PMHandler implements Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 3) {
                System.out.println(name +",组长已经同意您的请假审批!");
            }
        }
    }
    
    public class DirectorHandler implements Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 7) {
                System.out.println(name + ",中心总监已经同意您的请假审批");
            }
        }
    }
    
    public class MinisterHandler implements Handler {
        @Override
        public void handleRequest(String name, int days) {
            if (days <= 15) {
                System.out.println(name + ",部长已经同意您的请假审批");
            }
        }
    }
    
    // 责任链类
    public class HandlerChain implements Handler {
        private List<Handler> handlerList;
    
        public HandlerChain() {
            this.handlerList = new ArrayList<>();
        }
    
        public HandlerChain addHandler(Handler handler) {
            handlerList.add(handler);
            return this;
        }
    
    
        @Override
        public void handleRequest(String name, int days) {
            for (Handler handler : handlerList) {
                handler.handleRequest(name, days);
            }
        }
    }
    
    // 客户类
    public class OASystem {
        public static void main(String[] args) {
            // 创建具体处理者
            Handler pm = new PMHandler();
            Handler director = new DirectorHandler();
            Handler minister = new MinisterHandler();
            // 构建责任链
            HandlerChain chain = new HandlerChain()
                    .addHandler(pm)
                    .addHandler(director)
                    .addHandler(minister);
    
            // 使用责任链
            chain.handleRequest("王二", 10);
        }
    }
    

2.5 优缺点分析

2.5.1 优点

责任链模式的优点如下:

  • 降低了对象之间的耦合度
    • 发送者无需知道最终由哪一个handler处理其请求,也无需知道请求的传递过程
    • handler无需知道是哪一个发送者发送的请求,就像奖学金评选系统一样,本科生、研究生和博士生都可能是申请者
  • 增强了系统的可扩展性
    • 如果新增handler无需修改请求者的代码逻辑,满足开闭原则
  • 增强了给对象指派职责的灵活性
    • 随着工作流程的变化,可以动态地改变链内的handler、可以动态地调整handler的次序,还可以动态地增加或删除handler
  • 简化了对象之间的连接
    • 每个对象只需保持一个指向其后继者的next引用,不需保持其他所有处理者的引用,避免了使用众多的 if···else 语句。
  • 责任分担
    • 每个handler只需要处理自己分内的工作,分外的工作可以传递给下一个handler去完成
    • 这样的话,每个handler的分工明确,符合类的单一职责原则

自己对增强了给对象指派职责的灵活性的理解

  • 以请假审批为例,使用list记录责任链
    • 可以通过set操作,将组长换成方向负责人
    • 可以通过swap操作,调整部长和总监的审批顺序(不太恰当的例子😂)
    • 可以通过add(index, element)操作,在指定位置增加handler
    • 可以通过remove操作删除handler

2.5.2 缺点

责任链模式具有如下缺点:

  • 不能保证每个请求一定被处理:传入责任链的请求,可能直到链的末尾都没有对应的handler可以处理它
  • 对比较长的责任链,请求的处理可能涉及多个handler,系统性能将受到一定影响。
  • 责任链建立的合理性由客户端保证,增加了客户端的复杂性
    • 可能会由于责任链的错误设置而导致系统出错,如循环调用
    • 自己的疑惑: 为什么要由客户端创建责任链,权利下放很可能会出大问题的?😟

3. 从源码学习责任链

3.1 Tomcat的ApplicationFilterChain

3.1.1 关于Filter

  • 基于tomcat源码进行学习

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.0.41</version>
        </dependency>
        <!-- Thanks for using https://jar-download.com -->
    </dependencies>
    
  • 说到Filter(过滤器),笔者最大的印象:

    • 通过Filter可以对请求进行预处理,比如ip地址白名单,决定客户端的请求是否被处理,RemoteHostFilter
    • 通过Filter还能对响应结果进行处理,比如在响应头中添加信息,HttpHeaderSecurityFilter
  • Filter在请求进入容器后、未进入servlet之前,对请求进行预处理;同时,Filter可以在servlet返回响应后、未返回给客户端之前,对响应进行处理
    Java的责任链模式

  • tomcat的源码中,定义了Filter接口

    public interface Filter {
        public void init(FilterConfig filterConfig) throws ServletException;
        public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException;
        public void destroy();
    }
    
  • init()方法:servlet容器在实例化Filter对象后、调用Filter对象的doFilter方法前,调用init()方法(仅一次)以表明该filter可以提供服务

  • doFilter()方法:

    • 请求/响应对(pair)在FilterChain上传递,调用链上filter的doFilter()方法可以处理请求/响应对
    • 方法入参中的FilterChain 是构成责任链的关键,通过调用chain的doFilter()方法,可以实现请求/响应对的传递
    • FilterChain的doFilter()方法中,会通过例如数组下标pos的形式调用下一个filter
    • 因此,形成了一个调用链(责任链):chain.doFilter()调用filter1 --> filter1.doFilter()执行处理逻辑、调用chain.doFilter() --> chain.doFilter()调用filter2 --> filter2.doFilter()执行处理逻辑、调用chain.doFilter() -->
  • destroy()方法:

    • 容器调用filter的destroy()方法,以表明该filter不再提供服务
    • 仅当filter的doFilter()方法中,所有线程都退出或超时,容器才会调用destroy()

3.1.2 FilterChain

  • FilterChain接口是filter责任链接口,接口中doFilter()方法的实现逻辑可以保证请求/响应对在链上的传递与处理
    public interface FilterChain {
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException;
    }
    
  • 如果存在下一个filter,则调用链上的下一个filter处理请求/响应对
  • 如果不存在下一个filter,则调用链末尾的资源,如servlet的service()方法

3.1.3 ApplicationFilterChain

  • ApplicationFilterChain有四个重要的成员变量:
    • filters:ApplicationFilterConfig类型的数组,记录FilterChain上的filter;初始化长度为0,使用时按照INCREMENT = 10的增量进行扩容
    • pos:保存FilterChain当前位置的int变量,实际就是filters数组的索引,可以指向某个filter
    • n:FilterChain的实际大小,也就是filters数组中存储的filter的数量;filters数组每次按照10进行扩容,这是filters数组的容量,并非filter的数量
    • servlet:FilterChain上的filter访问到链尾时,将执行servlet实例的service()方法;通过servlet的service()方法,实现对用户请求的处理
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
    private int pos = 0;
    private int n = 0;
    private Servlet servlet = null;
    

ApplicationFilterChain相关方法的简单介绍

  • ApplicationFilterFactorycreateFilterChain()是一个工厂方法,负责完成ApplicationFilterChain的创建,包括设置servlet、向链中添加filter等

    public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
    	// 为FilterChain添加filter的关键代码
    	ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName());
    	// ...,其他代码省略
    	filterChain.addFilter(filterConfig);
    }
    
  • ApplicationFilterChain的addFilter()方法,被createFilterChain()方法调用,涉及filter是否重复添加的校验、filters数组的扩容、filter的添加

    void addFilter(ApplicationFilterConfig filterConfig) {}
    

doFilter()方法(重头戏)

  • ApplicationFilterChain的doFilter()方法源码如下,从代码不难看出:doFilter()方法最终将调用internalDoFilter()方法

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                // 省略异常的处理
            }
        } else {
            internalDoFilter(request,response);
        }
    }
    
  • internalDoFilter()方法:

    • 利用pos变量,从头开始逐个访问FilterChain上的filter,调用filter的doFilter()方法处理请求/响应对
    • 如果FilterChain上的所有所有filter都已经执行完毕,则执行servlet的service()方法处理请求
    private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                // ... 省略代码
                if( Globals.IS_SECURITY_ENABLED ) {
                    // ... 省略代码
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
    
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                          filter, request, response);
            } 
            // ... 省略catch
            return; // 巧妙的return处理,后续会提到原因
        }
    
        try {
            // ... 省略代码
            if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
                if( Globals.IS_SECURITY_ENABLED ) {
                    // ... 省略代码
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);
                } else {
                    servlet.service(request, response);
                }
            } else {
                servlet.service(request, response);
            }
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                      servlet, request, response);
        } 
        // ... 省略catch和finally
    }
    
  • 笔者在看到这里时,整个人是懵逼的:pos的更新确实能沿着FilterChain调用其上的filter,问题是开头是if (pos < n)并非while(pos < n),这咋能实现循环呢?别骗我读书少哦 😠

  • 耐下性子,点开filter的doFilter()方法,发现奥妙就在这里:filter的doFilter()方法,都会在合适的时机调用传入的chain的doFilter()方法

  • 例如,FailedRequestFilter的doFilter()方法

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!isGoodRequest(request)) {
            FailReason reason = (FailReason) request.getAttribute(Globals.PARAMETER_PARSE_FAILED_REASON_ATTR);
    
            int status;
    
            switch (reason) {
                // 为各种错误类型设置status
            }
            // 执行失败,直接返回携带错误信息的响应
            ((HttpServletResponse) response).sendError(status);
            return;
        }
        // 执行成功,继续访问FilterChain上的下一个filter
        chain.doFilter(request, response);
    }
    
  • RemoteHostFilter的doFilter()方法:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        process(request.getRemoteHost(), request, response, chain);
    }
    
    protected void process(String property, ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (isAllowed(property)) { // 请求被允许,继续访问FilterChain上的下一个Filter
            chain.doFilter(request, response);
        } else {
            // 请求不被允许,则返回携带错误信息的响应
        }
    }
    
  • 也就是说,FilterChain的doFilter()方法和filter的doFilter()方法,通过相互调用形成了一条处理请求/响应对的责任链
    Java的责任链模式

FilterChain的doFilter()方法中的return语句

  • return语句的作用:避免多次调用servlet.service()方法
  • 场景一: FilterChain上的filter均成功执行
    • 从上面的分析可知,如果所有的filter都成功执行,最终将会将会访问到FilterChain上的最后一个filter
    • 此时,最后一个filter的doFilter()方法将调用chain.doFilter()方法,发现pos < n条件不满足,则if语句之外的diamante,即调用servlet.service()方法
    • servlet.service()方法执行完成后,从最后一个filter的doFilter()方法退出
    • 此时,如果没有return语句,会再次执行servlet.service()方法
  • 场景二: filter处理请求失败
    • 如果filter处理请求/响应对时失败,例如,发现客户端IP不是受信任的(RemoteHostFilter),发现请求体格式不对(FailedRequestFilter)
    • 此时,都将直接返回携带错误信息的响应,最终会从filter的doFilter()方法中返回
    • 如果不存在return语句,则会继续调用servlet.service()方法处理请求
    • 其实,这样的情况下,根本无需调用servlet.service()方法处理请求

3.2 其他的责任链源码

  • 在工作的过程中,笔者也看到过责任链模式的源码。这里不再一一赘述,只做简要记录

  • ServletHandler.Chain也使用了责任链模式(貌似servlet的请求/响应对的处理都喜欢使用责任链模式)

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>10.0.7</version>
    </dependency>
    

4. 总结

关于责任链的实现

  • 笨方法:
    • 每个handler中的都包含指向下一个handler的next引用,根据handler自身的处理逻辑,每个handler可以选择是否处理请求、是否将请求传递给下一个handler
    • 笨方法的缺点:增加、删除或者改动handler在责任链中的次序,容易出错
  • 基于List或数组构建责任链:由专门的责任链类(FilterChainFacotry这样的类)负责构建责任链,请求在责任链上的传递可以参考ApplicationFilterChain的实现

参考文档

  • 理论知识:责任链模式(职责链模式)详解
  • 责任链模式的代码完善:Java设计模式13:责任链模式
  • ApplicationFilterChain作为源码阅读实例的灵感来源:从源码角度理解Java设计模式——责任链模式
  • 后续可以深入学习拦截器:
    • 利用责任链模式设计一个拦截器
    • 「造个轮子」——cicada 源码分析
    • 轻量级web框架cicada----(转)
版权声明:程序员胖胖胖虎阿 发表于 2022年11月4日 下午9:48。
转载请注明:Java的责任链模式 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...