聊一聊责任链模式

news2024/12/25 12:23:15

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

一、概述

责任链模式(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<T, R> {
    /**
     * 抽象处理类
     * @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<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;
    }
}
复制代码

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;
    }

}
复制代码

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<String, String> {
    /**
     * 处理加签逻辑
     * @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<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);
    }
	// ...

}
复制代码

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】图算法和LRUCache

文章目录最小生成树1.最小生成树概念2.Kruskal算法3.Prim算法最短路径算法单源最短路径--Dijkstra算法单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法LRUCacheLRUCache实现源码地址最小生成树 1.最小生成树概念 连通图中的每一棵生成树&#xff0c;都是原图…

软硬协同:基于倚天的视频云编码性能升级

算力时代&#xff0c;靠吃「硬件红利」便能搞定新应用场景的「甜蜜期」已经过去。人类社会的每一次科技跃迁&#xff0c;其本质都是计算力的突破与演进。 算盘拨出农耕文明的繁荣&#xff0c;机械计算机催生出第一次工业革命的袅袅蒸汽&#xff0c;而云计算的发展让万物互联成…

Spark零基础入门实战(二)Scala基础之数据类型

在Scala中,所有的值都有一个类型,包括数值和函数。如图1-4所示,说明了Scala的类型层次结构。 Any是Scala类层次结构的根,也被称为超类或顶级类。Scala执行环境中的每个类都直接或间接地从该类继承。该类中定义了一些通用的方法,例如equals()、hashCode()和toString()。…

【畅购商城】微信支付之支付模块

目录 支付页面 接口 后端实现 ​​​​​​​前端实现​​​​​​​ ​​​​​​​支付页面 步骤一&#xff1a;创建 flow3.vue组件 步骤二&#xff1a;引入第三方资源&#xff08;js、css&#xff09; <script> import TopNav from ../components/TopNav impor…

[附源码]Python计算机毕业设计Django疫情物资管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

电动汽车 (BEV) 优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

Vue3中的pinia使用(收藏版)

1.pinia介绍 pinia 是 Vue 的存储库&#xff0c;它允许您跨组件/页面共享状态。就是和vuex一样的实现数据共享。 依据Pinia官方文档&#xff0c;Pinia是2019年由vue.js官方成员重新设计的新一代状态管理器&#xff0c;更替Vuex4成为Vuex5。 Pinia 目前也已经是 vue 官方正式的状…

原创 | 传统医药零售如何实现数字化转型

一、数字化转型是零售药店未来实现增长的必由之路“十四五”规划将清洁能源、军事、消费、医药以及高端制造等行业定为未来需要重点关注的领域。围绕“医保、医疗、医药”三条主线&#xff0c;医药行业推出了一系列重要的改革措施&#xff0c;即将进入有史以来最大变革的黄金时…

【Mysql 基础知识】

一、引言 #1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。 文件&#xff08;File&#xff09;存储数据&#xff0c;保存在硬盘上&#xff0c;属于持久…

Talk | 香港大学在读博士生马逴凡:重新思考分辨率对于高效视频理解的意义

本期为TechBeat人工智能社区第460期线上Talk&#xff01; 北京时间12月7日(周三)20:00&#xff0c;香港大电子电机在读博士生——马逴凡的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “重新思考分辨率对于高效视频理解的意义 ”&#xff0c;届时…

web课程设计:HTML非遗文化网页设计题材【京剧文化】HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

12.8 - 每日一题 - 408

每日一句&#xff1a;成功与不成功之间有时距离很短只要后者再向前几步。 数据结构 1 若用冒泡排序方法对序列&#xff08;12&#xff0c;24&#xff0c;36&#xff0c;48&#xff0c;60&#xff0c;72&#xff09;从大到小排序&#xff0c;需要的比较次数是_____ A. 5 B. 1…

Tableau制作三元相图/三角图(Ternary/Triangle Chart)

三元相图 三角图就是有三个轴的坐标图&#xff0c;可以分别表示一个点在X、Y、Z三个维度上的不同比例情况。 与传统的XY坐标轴不同。XY坐标轴是有两个维度&#xff0c;而且每个维度没有上下限的限制。三角图中每个维度都在0-1的范围内&#xff0c;按百分比划分&#xff0c;即…

图像处理算法实战应用案例精讲-【目标检测】YOLO

前言 物体检测——顾名思义就是通过深度学习算法检测图像或视频中的物体。目标检测的目的是识别和定位场景中所有已知的目标。有了这种识别和定位,目标检测可以用来计数场景中的目标,确定和跟踪它们的精确位置,同时精确地标记它们。 目标检测通常与图像识别相混淆,所以在…

easyExcel 数据导入

前言 这段时间做了excel 数据导入&#xff0c;用的阿里巴巴的easyExcel 的代码。下面是官网和githab 代码地址。需要将github 上的代码拉下来&#xff0c;方便查看demo.关于Easyexcel | Easy ExcelEasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目&#xff0c;在尽…

[附源码]Python计算机毕业设计SSM基于与协同过滤算法的竞赛项目管理(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

可重复读实现原理

浅解释 概念 1、InnoDB 在每行记录后面保存两个隐藏的列&#xff0c;分别保存了数据行的创建版本号和删除版本号。每开始一个新的事务&#xff0c;系统版本号都会递增。事务开始时刻的系统版本号会作为事务的版本号&#xff0c;用来和查询到的每行记录的创建版本号对比。 2、i…

RKMEDIA--VP使用

本章描述rkmedia vp模块 即视频一入四处功能的介绍。 使用场景&#xff1a;主要用在DVR/DMS产品上&#xff0c;需要多路视频节点的输入和获取。 可以rv1126/rv1109可以外接模拟高清RX芯片&#xff08;NVP6188,TP2815等&#xff09;&#xff0c;达到8路camera输入的能力。 在…

PLSQL导出、导入数据 和 同步数据 以及 navicat 里同步数据 以及解决plsql导出数据乱码问题

PLSQL导出、导入数据 和 同步数据 以及 navicat 里同步数据 以及解决plsql导出数据乱码问题1. 导出数据1.1 导出.pde文件1.2 导出sql文件1.2.1 导出sql压缩文件1.2.1 导出问题——乱码 与 解决乱码问题1. 尝试解决方式12. 尝试解决方式23. 直接换PLSQ版本&#xff08;最终解决问…

Traysoft AddTapi.NET 支持多条线路

Traysoft AddTapi.NET 支持多条线路 使用Traysoft AddTapi。您可以轻松地将手机功能添加到C#、VB.NET和C应用程序中。添加Tapi。NET将为您提供开发移动应用程序所需的一切。包括&#xff1a;电话呼叫、接收者ID、语音邮件、警告系统、电话呼叫跟踪和监控。添加Tapi。NET继续联系…