聊一聊责任链模式

责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是
首页 新闻资讯 行业资讯 聊一聊责任链模式

一、概述

责任链模式(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);}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

抽象类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;}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

具体处理类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);}}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

当然还有具体的处理类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("链路待处理的数据");}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

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;}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

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

复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

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

handleMap:这个map会存放责任链路中,具体的执行类,key是注解@Duty中定义的type值,value是标记了@Duty注解的bean集合,也就是具体的执行类集合。

setHandleMap:传入具体执行bean的集合,存放在map中。

executeHandle:从map中找到具体的执行bean集合,并依次执行。

复制

/** * @author 往事如风 * @version 1.0 * @date 2022/10/25 16:00 * @description 责任链管理类 */public class HandleChainManager {/**     * 存放责任链路上的具体处理类     * k-具体业务场景名称     * v-具体业务场景下的责任链路集合     */private Map<String, List<IHandler>> handleMap;/**     * 存放系统中责任链具体处理类     * @param handlerList     */public void setHandleMap(List<IHandler> 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 <T, R> R executeHandle(String type, T t){List<IHandler> handlers = handleMap.get(type);R r = null;if (CollectionUtil.isNotEmpty(handlers)) {for (IHandler<T, R> handler : handlers) {   r = handler.handle(t);}}return r;}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

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

复制

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

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

5、具体的处理类:SignChainHandler、EncryptionChainHandler、RequestChainHandler,这里我以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<String, String> {/**     * 处理加签逻辑     * @param s     * @return     */@Override
    public String handle(String s){// 加签逻辑
        System.out.println("甲方爸爸要求加签");return "加签";}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

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;}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

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;}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

图片

下面,我们来看下其中一个子类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;}// ...}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

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

复制

public class PerpetualCache implements Cache {private final String id;private final Map<Object, Object> 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);}
 // ...}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

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);    }   }  } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

  • 42.

  • 43.

  • 44.

  • 45.

  • 46.

  • 47.

  • 48.

  • 49.

  • 50.

  • 51.

  • 52.

  • 53.

  • 54.

  • 55.

  • 56.

  • 57.

  • 58.

  • 59.

  • 60.

  • 61.

  • 62.

  • 63.

  • 64.

  • 65.

  • 66.

  • 67.

  • 68.

  • 69.

  • 70.

  • 71.

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;}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

在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);}}}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

获取拦截器,执行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);}}}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

五、总结

5.1 优点

  • 将请求与处理解耦。

  • 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,转发给下一个节点。

  • 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。

  • 链路结构灵活,可以通过改变链路的结构动态的新增或删减责任。

  • 易于扩展新的请求处理类(节点),符合开闭原则。

5.2 缺点

责任链太长或者处理时间过长,会影响整体性能。

如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。

六、参考源码

编程文档: https://gitee.com/cicadasmile/butte-java-note

应用仓库: https://gitee.com/cicadasmile/butte-flyer-parent


27    2022-11-01 08:46:20    责任链 模式 对象