• 聊一聊责任链模式


    将一堆“事情”串联在一起,有序执行,就叫责任链

    一、概述

    责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止,属于行为型模式。
    下面放一张足球比赛的图,通过层层传递,最终射门。通过这张图,可以更好的理解责任链模式。

    二、入门案例

    2.1 类图

    2.2 基础类介绍

    抽象接口RequestHandler

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 13:41
     * @description
     */
    public interface RequestHandler {
    
        void doHandler(String req);
    }
    

    抽象类BaseRequestHandler

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 13:45
     * @description
     */
    public abstract class BaseRequestHandler implements RequestHandler {
    
        protected RequestHandler next;
    
        public void next(RequestHandler next) {
            this.next = next;
        }
    }
    

    具体处理类AHandler

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 14:00
     * @description
     */
    public class AHandler extends BaseRequestHandler {
    
        @Override
        public void doHandler(String req) {
            // 处理自己的业务逻辑
            System.out.println("A中处理自己的逻辑");
            // 传递给下个类(若链路中还有下个处理类)
            if (next != null) {
                next.doHandler(req);
            }
        }
    }
    
    

    当然还有具体的处理类B、C等等,这里不展开赘述。
    使用类Client

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 14:06
     * @description
     */
    public class Client {
        public static void main(String[] args) {
            BaseRequestHandler a = new AHandler();
            BaseRequestHandler b = new BHandler();
            BaseRequestHandler c = new CHandler();
            a.next(b);
            b.next(c);
            a.doHandler("链路待处理的数据");
        }
    }
    

    2.3 处理流程图

    三、应用场景

    3.1 场景举例

    场景一

    前两年,在一家金融公司待过一段时间,其中就有一个业务场景:一笔订单进来,会先在后台通过初审人员进行审批,初审不通过,订单流程结束。初审通过以后,会转给终审人员进行审批,不通过,流程结束;通过,流转到下个业务场景。
    对于这块业务代码,之前一代目是一个叫知了的同事,他撸起袖子就是干,一套if-else干到底。后来,技术老大CodeReview,点名要求改掉这块。于是乎,想到用用设计模式吧,然后就噼里啪啦一顿改。(当然,比较复杂的情况,还是可以用工作流来处理这个场景,当时碍于时间成本,也就放弃了)。

    场景二

    上家公司对接甲方爸爸的时候,对方会调用我们接口,将数据同步过来。同样,我们需要将处理好的数据,传给他们。由于双方传输数据都是加密传输,所以在接受他们数据之前,需要对数据进行解密,验签,参数校验等操作。同样,我们给他们传数据也需要进行加签,加密操作。

    具体案例

    话不多说,对于场景二,我来放一些伪代码,跟大家一起探讨下。
    1、一切从注解开始,我这里自定义了一个注解@Duty,这个注解有spring的@Component注解,也就是标记了这个自定义注解的类,都是交给spring的bean容器去管理。
    注解中,有两个属性:1.type,定义相同的type类型的bean,会被放到一个责任链集合中。2.order,同一个责任链集合中,bean的排序,数值越小,会放到链路最先的位置,优先处理。

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 16:11
     * @description
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Service
    public @interface Duty {
        /**
         * 标记具体业务场景
         * @return
         */
        String type() default "";
    
        /**
         * 排序:数值越小,排序越前
         * @return
         */
        int order() default 0;
    }
    

    2、定义一个顶层的抽象接口IHandler,传入2个泛型参数,供后续自定义。

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 15:31
     * @description 责任链顶层抽象类
     */
    public interface IHandler {
        /**
         * 抽象处理类
         * @param t
         * @return
         */
        R handle(T t);
    }
    

    3、定义一个责任链bean的管理类HandleChainManager,用来存放不同业务下的责任链路集合。在该类中,有一个Map和两个方法。

    1. handleMap:这个map会存放责任链路中,具体的执行类,key是注解@Duty中定义的type值,value是标记了@Duty注解的bean集合,也就是具体的执行类集合。
    2. setHandleMap:传入具体执行bean的集合,存放在map中。
    3. executeHandle:从map中找到具体的执行bean集合,并依次执行。
    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 16:00
     * @description 责任链管理类
     */
    public class HandleChainManager {
        /**
         * 存放责任链路上的具体处理类
         * k-具体业务场景名称
         * v-具体业务场景下的责任链路集合
         */
        private Map> handleMap;
    
        /**
         * 存放系统中责任链具体处理类
         * @param handlerList
         */
        public void setHandleMap(List handlerList) {
            handleMap = handlerList
                    .stream()
                    .sorted(Comparator.comparingInt(h -> AnnotationUtils.findAnnotation(h.getClass(), Duty.class).order()))
                    .collect(Collectors.groupingBy(handler -> AnnotationUtils.findAnnotation(handler.getClass(), Duty.class).type()));
        }
    
        /**
         * 执行具体业务场景中的责任链集合
         * @param type 对应@Duty注解中的type,可以定义为具体业务场景
         * @param t 被执行的参数
         */
        public  R executeHandle(String type, T t) {
            List handlers = handleMap.get(type);
            R r = null;
            if (CollectionUtil.isNotEmpty(handlers)) {
                for (IHandler handler : handlers) {
                   r = handler.handle(t);
                }
            }
            return r;
        }
    }
    

    4、定义一个配置类PatternConfiguration,用于装配上面的责任链管理器HandleChainManager

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 15:35
     * @description 设计模式配置类
     */
    @Configuration
    public class PatternConfiguration {
    
        @Bean
        public HandleChainManager handlerChainExecute(List handlers) {
            HandleChainManager handleChainManager = new HandleChainManager();
            handleChainManager.setHandleMap(handlers);
            return handleChainManager;
        }
    
    }
    

    5、具体的处理类:SignChainHandlerEncryptionChainHandlerRequestChainHandler,这里我以SignChainHandler为例。
    在具体处理类上标记自定义注解@Duty,该类会被注入到bean容器中,实现IHandler接口,只需关心自己的handle方法,处理具体的业务逻辑。

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/10/25 15:31
     * @description 加签类
     */
    @Duty(type = BusinessConstants.REQUEST, order = 1)
    public class SignChainHandler implements IHandler {
        /**
         * 处理加签逻辑
         * @param s
         * @return
         */
        @Override
        public String handle(String s) {
            // 加签逻辑
            System.out.println("甲方爸爸要求加签");
            return "加签";
        }
    }
    

    6、具体怎么调用?这里我写了个测试controller直接调用,具体如下:

    /**
     * @author 往事如风
     * @version 1.0
     * @date 2022/9/6 17:32
     * @description
     */
    @RestController
    @Slf4j
    public class TestController {
    
        @Resource
        private HandleChainManager handleChainManager;
    
        @PostMapping("/send")
        public String duty(@RequestBody String requestBody) {
            String response = handleChainManager.executeHandle(BusinessConstants.REQUEST, requestBody);
            return response;
        }
    }
    

    7、执行结果,会按照注解中标记的order依次执行。

    至此,完工。又可以开心的撸代码了,然后在具体的执行类中,又是一顿if-else。。。

    四、源码中运用

    4.1Mybatis源码中的运用

    Mybatis中的缓存接口Cache,cache作为一个缓存接口,最主要的功能就是添加和获取缓存的功能,作为接口它有11个实现类,分别实现不同的功能,下面是接口源码和实现类。

    package org.apache.ibatis.cache;
    
    import java.util.concurrent.locks.ReadWriteLock;
    
    public interface Cache {
        String getId();
    
        void putObject(Object var1, Object var2);
    
        Object getObject(Object var1);
    
        Object removeObject(Object var1);
    
        void clear();
    
        int getSize();
    
        default ReadWriteLock getReadWriteLock() {
            return null;
        }
    }
    

    下面,我们来看下其中一个子类LoggingCache的源码。主要看他的putObject方法和getObject方法,它在方法中直接传给下一个实现去执行。这个实现类其实是为了在获取缓存的时候打印缓存的命中率的。

    public class LoggingCache implements Cache {
        private final Log log;
        private final Cache delegate;
        protected int requests = 0;
        protected int hits = 0;
    
        public LoggingCache(Cache delegate) {
            this.delegate = delegate;
            this.log = LogFactory.getLog(this.getId());
        }
    
        // ...
        public void putObject(Object key, Object object) {
            this.delegate.putObject(key, object);
        }
    
        public Object getObject(Object key) {
            ++this.requests;
            Object value = this.delegate.getObject(key);
            if (value != null) {
                ++this.hits;
            }
    
            if (this.log.isDebugEnabled()) {
                this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio());
            }
    
            return value;
        }
        // ...
    }
    

    最后,经过Cache接口各种实现类的处理,最终会到达PerpetualCache这个实现类。与之前的处理类不同的是,这个类中有一个map,在map中做存取,也就是说,最终缓存还是会保存在map中的。

    public class PerpetualCache implements Cache {
        private final String id;
        private final Map cache = new HashMap();
    
        public PerpetualCache(String id) {
            this.id = id;
        }
    
    	// ...
    
        public void putObject(Object key, Object value) {
            this.cache.put(key, value);
        }
    
        public Object getObject(Object key) {
            return this.cache.get(key);
        }
    	// ...
    
    }
    

    4.2spring源码中的运用

    4.2.1DispatcherServlet类

    DispatcherServlet 核心方法 doDispatch。HandlerExecutionChain只是维护HandlerInterceptor的集合,可以向其中注册相应的拦截器,本身不直接处理请求,将请求分配给责任链上注册处理器执行,降低职责链本身与处理逻辑之间的耦合程度。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    			try {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == null) {
    					noHandlerFound(processedRequest, response);
    					return;
    				}
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    				// Process last-modified header, if supported by the handler.
    				String method = request.getMethod();
    				boolean isGet = "GET".equals(method);
    				if (isGet || "HEAD".equals(method)) {
    					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    						return;
    					}
    				}
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// making them available for @ExceptionHandler methods and other scenarios.
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				// Clean up any resources used by a multipart request.
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}
    

    4.2.2HandlerExecutionChain类

    这里分析的几个方法,都是从DispatcherServlet类的doDispatch方法中请求的。

    • 获取拦截器,执行preHandle方法
    boolean applyPreHandle(HttpServletRequest request, 
                           HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }
        return true;
    }
    
    • 在applyPreHandle方法中,执行triggerAfterCompletion方法
    void triggerAfterCompletion(HttpServletRequest request, 
                                HttpServletResponse response, Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for(int i = this.interceptorIndex; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                } catch (Throwable var8) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
                }
            }
        }
    }
    
    • 获取拦截器,执行applyPostHandle方法
    void applyPostHandle(HttpServletRequest request, 
                         HttpServletResponse response, ModelAndView mv) 
                         throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }
    

    五、总结

    5.1 优点

    1. 将请求与处理解耦。
    2. 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,转发给下一个节点。
    3. 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
    4. 链路结构灵活,可以通过改变链路的结构动态的新增或删减责任。
    5. 易于扩展新的请求处理类(节点),符合开闭原则

    5.2 缺点

    1. 责任链太长或者处理时间过长,会影响整体性能。
    2. 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。

    六、参考源码

    编程文档:
    https://gitee.com/cicadasmile/butte-java-note
    
    应用仓库:
    https://gitee.com/cicadasmile/butte-flyer-parent
    
  • 相关阅读:
    解决Matlab中文乱码问题,再也不用升级Matlab版本啦
    全志A40i开发板(4核ARM Cortex-A7)测评合集——Qt性能测试
    解决Python requests库中的重定向问题
    Java常用类(二)
    一、综合——通信职业道德
    暑假好看的日剧来啦~~
    【不积跬步无以至千里】Linux批量新建分区
    Marked.js让您的文档编辑更加轻松自如!
    【新的小主机】向日葵远程控制ubuntu
    Spring学习第5篇:自动注入(autowire)详解
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/16841111.html