• 设计模式学习笔记(十四)责任链模式实现及在Filter中的应用


    责任链模式(Chain Of Responsibility Design Pattern),也叫做职责链,是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

    责任链模式

    一、责任链模式介绍#

    其实在日常生活中,有不少使用责任链的场景。比如公司采购审批流程,需要各个部门领导的批准同意。在责任链模式中,客户端只需要将请求发送到责任链上,无须关心请求的处理细节和传递,请求会自动进行传递。

    1.1 责任链模式的结构#

    责任链模式的结构大致如下所示:

    image-20220404203259612

    • abstract Handler:抽象处理者,定义一个处理请求的接口,内部包含抽象处理方法和后继具体处理者
    • Handler1、Handler2:具体处理者,具体实现抽象处理者的方法,并对请求做一些逻辑处理
    • Client:客户端,使用职责链模式

    1.2 责任链模式的实现#

    根据上面的类图,可以实现如下代码:

    /**
     * @description: 抽象处理类
     * @author: wjw
     * @date: 2022/4/4
     */
    public abstract class Handler {
    
        private Handler successor;
    
        public Handler getSuccessor() {
            return successor;
        }
    
        public void setSuccessor(Handler successor) {
            this.successor = successor;
        }
    
        /**
         * 处理请求的抽象方法
         * @param request 请求
         */
        public abstract void handleRequest(String request);
    }
    /**
     * @description: 具体处理者1
     * @author: wjw
     * @date: 2022/4/4
     */
    public class Handler1 extends Handler{
    
        private String handler;
    
        public Handler1(String handler) {
            this.handler = handler;
        }
    
        @Override
        public void handleRequest(String request) {
            if ("handler1".equals(request)) {
                System.out.println("具体处理者handler1进行请求处理");
            } else {
                if (getSuccessor() != null) {
                    //如果指向下一个具体处理者
                    getSuccessor().handleRequest(request);
                } else {
                    System.out.println("没有处理者进行处理");
                }
            }
        }
    }
    /**
     * @description: 具体处理者2
     * @author: wjw
     * @date: 2022/4/4
     */
    public class Handler2 extends Handler{
    
        private String handler;
    
        public Handler2(String handler) {
            this.handler = handler;
        }
    
        @Override
        public void handleRequest(String request) {
            if ("handler2".equals(request)) {
                System.out.println("具体处理者handler2进行请求处理");
            } else {
                if (getSuccessor() != null) {
                    getSuccessor().handleRequest(request);
                } else {
                    System.out.println("请求没有被任何处理者处理");
                }
            }
        }
    }
    /**
     * @description: 客户端类
     * @author: wjw
     * @date: 2022/4/4
     */
    public class Client {
        public static void main(String[] args) {
            Handler handler1 = new Handler1("handler1");
            Handler handler2 = new Handler2("handler2");
            handler1.setSuccessor(handler2);
            handler1.handleRequest("handler1");
            handler1.handleRequest("handler2");
    
        }
    }
    

    测试结果:

    具体处理者handler1进行请求处理
    具体处理者handler2进行请求处理
    

    二、责任链模式的应用场景#

    职责链模式最常见的应用就是用来开发各种框架的过滤器和拦截器,比如Spring Interceptor和Servlet Filter

    2.1 在Servlet Filter中的应用#

    Filter 可以实现对HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。比如一些Servlet 容器(TomCat、Jetty等)就支持Filter的过滤功能。以TomCat 为例:

    image-20220404214532930

    所以当Servlet 请求到来时,首先会经过Filter 处理,最后再到达Servlet实例。我这里选取的TomCat版本是SpringBoot自带的9.0,先来看看FilterChain 的接口:

    public interface FilterChain {
        //Filter具体实现
        void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
    }
    

    ApplicationFilterChain是责任链模式的具体实现类:

    public final class ApplicationFilterChain implements FilterChain {
        private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; //Filter配置数组
        private int pos = 0; //执行Filter的序号
        private int n = 0;	 //目前Filter的个数
        private Servlet servlet = null;
    
        public ApplicationFilterChain() {
        }
        
    	@Override
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            //Filter 具体实现
            //...
        }
        
        public static ServletRequest getLastServicedRequest() {
            return (ServletRequest)lastServicedRequest.get();
        }
    
        public static ServletResponse getLastServicedResponse() {
            return (ServletResponse)lastServicedResponse.get();
        }
    
        void addFilter(ApplicationFilterConfig filterConfig) {
            ApplicationFilterConfig[] newFilters = this.filters;
            int var3 = newFilters.length;
    
            for(int var4 = 0; var4 < var3; ++var4) {
                ApplicationFilterConfig filter = newFilters[var4];
                if (filter == filterConfig) {
                    return;
                }
            }
    		//增加Filter
            if (this.n == this.filters.length) {
                newFilters = new ApplicationFilterConfig[this.n + 10];
                System.arraycopy(this.filters, 0, newFilters, 0, this.n);
                this.filters = newFilters;
            }
    
            this.filters[this.n++] = filterConfig;
        }
    }
    

    2.2 在Spring Interceptor中的应用#

    和Servlet Filter类似,在Spring 中也有对应的过滤器 Interceptor。它是由Spring MVC 框架来实现,借一张来自《设计模式之美》的图片来说明:

    image-20220404221601450

    客户端发送请求,首先会经过Servlet Filter,然后再经过Spring Interceptor,最后再到达具体的业务中。

    和Filter一样,Interceptor 中也是基于责任链模式来实现的,与之相对的HandlerInterceptor是抽象处理接口:

    public interface HandlerInterceptor {
        default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return true;
        }
    
        default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        }
    
        default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        }
    }
    

    会有各种具体处理类的实现:

    image-20220404222327991

    此外在其他框架中也存在着职责链模式的使用,比如Dubbo Filter、Netty ChannelPipeline等等

    三、责任链模式实战#

    模拟在618大促期间的业务系统上线审批流程场景:

    像是这些⼀线电商类的互联⽹公司,阿⾥、京东、拼多多等,在618期间都会做⼀些运营活动场景以及 提供的扩容备战,就像过年期间百度的红包⼀样。但是所有开发的这些系统都需要陆续的上线,因为临 近618有时候也有⼀些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的, 也会相应的增强审批⼒度。就像⼀级响应、⼆级响应⼀样。

    ⽽这审批的过程在随着特定时间点会增加不同级别的负责⼈加⼊,每个⼈就像责任链模式中的每⼀个核 ⼼点。对于研发并不需要关⼼具体的审批流程处理细节,只需要知道这个上线更严格,级别也更 ⾼,但对于研发⼈员来说同样是点击相同的提审按钮,等待审核。

    image-20220404223156189

    使用责任链模式可以将各个服务模块按照一、二、三级进行分离,每个级别可以像Filter一样用Successor下一个级别的方法进行调用。具体结构图如下(来自《重学Java设计模式》)

    image-20220404223642273

    具体代码结构

    │——AuthInfo.java
    │——AuthLink.java
    ├─business
    │     AuthService.java
    └─impl
          Level1AuthLink.java
          Level2AuthLink.java
          Level3AuthLink.java
    
    

    实现代码:

    /**
     * @description: 链路抽象审批
     * @author: wjw
     * @date: 2022/4/4
     */
    public abstract class AuthLink {
    
        /**时间格式化**/
        protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        /**级别人员ID**/
        protected String levelUserId;
        /**级别人员姓名**/
        protected String levelUserName;
        /**下一个链路**/
        private AuthLink next;
    
        public AuthLink(String levelUserId, String levelUserName) {
            this.levelUserId = levelUserId;
            this.levelUserName = levelUserName;
        }
    
        public AuthLink next() {
            return next;
        }
    
        public AuthLink appendNext(AuthLink next) {
            this.next = next;
            return this;
        }
    
        /**每个具体处理者必须实现的方法**/
        public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
    }
    /**
     * @description: 责任链中返回对象的定义
     * @author: wjw
     * @date: 2022/4/4
     */
    public class AuthInfo {
    
        private String code;
        private String info = " ";
    
        public AuthInfo(String code, String... infos) { //...表示可变长参数,可以传入多个参数
            this.code = code;
            for (String s : infos) {
                this.info = this.info.concat(s);
            }
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getInfo() {
            return info;
        }
    
        public void setInfo(String info) {
            this.info = info;
        }
    }
    /**
     * @description: 一级审核人
     * @author: wjw
     * @date: 2022/4/4
     */
    public class Level1AuthLink extends AuthLink {
    
        public Level1AuthLink(String levelUserId, String levelUserName) {
            super(levelUserId, levelUserName);
        }
    
        @Override
        public AuthInfo doAuth(String uId, String orderId, Date authDate) {
            Date date = AuthService.queryAuthInfo(levelUserId, orderId);
            if (null == date) {
                return new AuthInfo("0001", "单号", orderId, "状态:一级审批负责人", levelUserName);
            }
            AuthLink next = super.next();
            if (null == next) {
                return new AuthInfo("0000", "单号", orderId, "状态:一级审批负责人", " 时间:", f.format(date), "审批人:", levelUserName);
            }
            return next.doAuth(uId, orderId, authDate);
        }
    }
    /**
     * @description: 测试类
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ApiTest {
        private Logger logger = LoggerFactory.getLogger(ApiTest.class);
    
        @Test
        public void test_AuthLink() throws ParseException {
            AuthLink authLink = new Level3AuthLink("1000013", "王工")
                    .appendNext(new Level2AuthLink("1000012", "张经理"))
                    .appendNext(new Level3AuthLink("1000011", "赵总裁"));
    
            logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));
    
            AuthService.auth("1000013", "100099800423123");
            logger.info("测试结果:{}", "模拟二级审批,王工");
            logger.info("测试结果:{}",JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));
    
            AuthService.auth("1000012", "100099800423123");
            logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
            logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));
    
            AuthService.auth("1000011", "100099800423123");
            logger.info("测试结果:{}", "模拟二级负责人审批,赵总裁");
            logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));
    
        }
    }
    

    最后的测试结果:

    10:45:14.496 [main] INFO  ApiTest - 测试结果:{"code":"0001","info":" 单号:100099800423123 状态:待三级审批 王工"}
    10:45:14.500 [main] INFO  ApiTest - 测试结果:模拟二级审批,王工
    10:45:14.500 [main] INFO  ApiTest - 测试结果:{"code":"0001","info":" 单号:100099800423123 状态:待三级审批 赵总裁"}
    10:45:14.500 [main] INFO  ApiTest - 测试结果:模拟二级负责人审批,张经理
    10:45:14.500 [main] INFO  ApiTest - 测试结果:{"code":"0001","info":" 单号:100099800423123 状态:待三级审批 赵总裁"}
    10:45:14.500 [main] INFO  ApiTest - 测试结果:模拟二级负责人审批,赵总裁
    10:45:14.500 [main] INFO  ApiTest - 测试结果:{"code":"0000","info":" 单号: 100099800423123 状态:三级审批完成 时间:2022-04-05 10:45:14 审批人:赵总裁"}
    

    参考资料#

    《设计模式之美》

    http://c.biancheng.net/view/4024.html

    《重学Java设计模式》

  • 相关阅读:
    相似性搜索:第 4 部分--分层可导航小世界 (HNSW)
    Mysql事务日志
    SpringCloud之gateway基本使用解读
    Linux 的端口区间及设置
    Set 集合概述与使用
    为何vxlan需要封装在UDP里而不是直接使用IP包封装?
    小白必看:如何查询微信账号权重?
    背包问题讨论
    Vue-cli全局配置整理
    「AntV」当我用AI为开发AntV图表插上想象的翅膀后
  • 原文地址:https://www.cnblogs.com/EthanWong/p/16101006.html