JAVA设计模式之责任链设计模式
来源:SegmentFault
时间:2023-02-22 16:26:05 322浏览 收藏
小伙伴们有没有觉得学习数据库很有意思?有意思就对了!今天就给大家带来《JAVA设计模式之责任链设计模式》,以下内容将会涉及到MySQL、Java、spring、springboot,若是在学习中对其中部分知识点有疑问,或许看了本文就能帮到你!
责任链模式:责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
何时使用:在处理消息的时候以过滤很多道。
如何解决:拦截的类都实现统一接口。
关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
废话不多说,直接上手:
项目背景:网关作为微服务项目入口,拦截客户端所有的请求实现权限控制,如判断API接口限流->黑名单拦截->用户会话->参数过滤
环境准备:JDK8、springboot2.0.x、idea工具、mysql数据库...
整体流程可以缩概为如下图所示:

1、定义抽象的handler接口
/** * 网关拦截器 * @author zhoumin * @create 2020-03-14 13:20 */ public abstract class GatewayHandler { /** * 使用抽象类定义共同的行为方法 */ public abstract void service(); }
2、定义具体实现事项
/** * @author zhoumin * @create 2020-03-14 13:25 */ public class ApiLimitHandler extends GatewayHandler { //这里需要指定下一个handler private BlacklistHandler blacklistHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("1、api接口限流........."); //下一个handler blacklistHandler.service(); } //指定下一个handler public void setNextGatewayHandler(BlacklistHandler blacklistHandler) { this.blacklistHandler = blacklistHandler; } }
/** * 黑名单拦截 * * @author zhoumin * @create 2020-03-14 13:26 */ public class BlacklistHandler extends GatewayHandler { //这里需要指定下一个handler private ConversationHandler conversationHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("2、黑名单拦截........."); //下一个handler conversationHandler.service(); } //指定下一个handler public void setNextGatewayHandler(ConversationHandler conversationHandler) { this.conversationHandler = conversationHandler; } }
/** * 用户会话拦截 * * @author zhoumin * @create 2020-03-14 13:27 */ public class ConversationHandler extends GatewayHandler { //这里需要指定下一个handler private ParamHandler paramHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("3、用户会话拦截........."); //下一个handler paramHandler.service(); } //指定下一个handler public void setNextGatewayHandler(ParamHandler paramHandler) { this.paramHandler = paramHandler; } }
/** * 参数拦截 * * @author zhoumin * @create 2020-03-14 13:30 */ public class ParamHandler extends GatewayHandler { /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("4、用户参数过滤........."); } }
注意:需要在每个具体handler里面执行下一个需要执行的handler,直到最后一个
这个时候发现,只要获取第一个handler后,并执行,那么整个链路就能顺利完成,那么怎么获取第一个handler呢?答案是:使用工厂模型
/** * 工厂创建对象 * * @author zhoumin * @create 2020-03-14 13:49 */ public class FactoryHandler { //这里static 为了方便后面调试运行 public static ApiLimitHandler getFirstGatewayHandler(){ ApiLimitHandler apiLimitHandler = new ApiLimitHandler(); BlacklistHandler blacklistHandler = new BlacklistHandler(); apiLimitHandler.setNextGatewayHandler(blacklistHandler); ConversationHandler conversationHandler = new ConversationHandler(); blacklistHandler.setNextGatewayHandler(conversationHandler); ParamHandler paramHandler = new ParamHandler(); conversationHandler.setNextGatewayHandler(paramHandler); return apiLimitHandler; } }
至此,完成了整个流程,现在可以来简单测试下
/** * @author zhoumin * @create 2020-03-14 14:49 */ @RestController public class HandlerController { @GetMapping("/clientHandler") public String clientHandler(){ ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler(); apiLimitHandler.service(); return "success!!"; } }
运行后可以看到控制台打印数据:

断点调试可以清晰看到整个链路结构:

我是一个分割线emmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.....
美中不足,我们发现代码有很多冗余,包括setNextGatewayHandler以及出现在每个handler中的下个handler,那么如何优化呢?------------->将公共方法抽取到父类中!
/** * 网关拦截器 * @author zhoumin * @create 2020-03-14 13:20 */ public abstract class GatewayHandler { protected GatewayHandler nextGatewayHandler; /** * 使用抽象类定义共同的行为方法 */ public abstract void service(); //设置下一个handler public void setNextGatewayHandler(GatewayHandler gatewayHandler){ this.nextGatewayHandler = gatewayHandler; } //执行下一个handler protected void nextService(){ if (nextGatewayHandler != null){ nextGatewayHandler.service(); } } }
子类优化:
/** * @author zhoumin * @create 2020-03-14 13:25 */ public class ApiLimitHandler extends GatewayHandler { //这里需要指定下一个handler // private BlacklistHandler blacklistHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("1、api接口限流........."); //下一个handler // blacklistHandler.service(); nextService(); } //指定下一个handler // public void setNextGatewayHandler(BlacklistHandler blacklistHandler) { // this.blacklistHandler = blacklistHandler; // } }
/** * 黑名单拦截 * * @author zhoumin * @create 2020-03-14 13:26 */ public class BlacklistHandler extends GatewayHandler { //这里需要指定下一个handler // private ConversationHandler conversationHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("2、黑名单拦截........."); //下一个handler // conversationHandler.service(); nextService(); } //指定下一个handler // public void setNextGatewayHandler(ConversationHandler conversationHandler) { // this.conversationHandler = conversationHandler; // } }
/** * 用户会话拦截 * * @author zhoumin * @create 2020-03-14 13:27 */ public class ConversationHandler extends GatewayHandler { //这里需要指定下一个handler // private ParamHandler paramHandler; /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("3、用户会话拦截........."); //下一个handler // paramHandler.service(); nextService(); } //指定下一个handler // public void setNextGatewayHandler(ParamHandler paramHandler) { // this.paramHandler = paramHandler; // } }
/** * 参数拦截 * * @author zhoumin * @create 2020-03-14 13:30 */ public class ParamHandler extends GatewayHandler { /** * 使用抽象类定义共同的行为方法 */ @Override public void service() { System.out.println("4、用户参数过滤........."); } }
运行结果跟上图保持一致。
至此,结束了吗?显然没有,继续.....
可以看到我们在Factory中定义的关系强耦合,如果现在我们需要改变顺序或者是新增其他handler,需要改动代码,可扩展性较差。
结合上一章,启示可以把beanId关系维护到数据库中,利用spring容器,根据beanId获取到每个具体的实例对象(注:需要将上面每一个具体handler注入到spring容器中)。
现有数据结构和数据关系如下:

可以很清晰看到,第一个handler的prev为null,next为我们指定的handler;而最后一个handler的next为null。
定义我们的实体类以及mapper如下:
/** * @author zhoumin * @create 2020-03-14 15:35 */ @Data public class GatewayHandlerEntity { /** 主键ID */ private Integer id; /** handler名称 */ private String handlerName; /** handler主键id */ private String handlerId; /** 下一个handler */ private String nextHandlerId; }
/** * @author zhoumin * @create 2020-03-14 15:33 */ @Mapper public interface GatewayHandlerMapper { /** * 获取第一个handler * @return */ GatewayHandlerEntity getFirstGatewayHandler(); /** * 根据beanId获取当前handler * @return */ GatewayHandlerEntity getByHandler(String handlerId); }
对应的service可以处理为:
/** * @author zhoumin * @create 2020-03-14 15:38 */ @Component public class GatewayHandlerService { @Resource private GatewayHandlerMapper gatewayHandlerMapper; private GatewayHandler firstGatewayHandler; public GatewayHandler getDbFirstGatewayHandler() { //判断是否加载过 if (this.firstGatewayHandler != null) { return this.firstGatewayHandler; } // 1.查询数据库第一个handler配置 GatewayHandlerEntity firstGatewayHandlerEntity = gatewayHandlerMapper.getFirstGatewayHandler(); if (firstGatewayHandlerEntity == null) { return null; } String firstBeanHandlerId = firstGatewayHandlerEntity.getHandlerId(); if (StringUtils.isEmpty(firstBeanHandlerId)) { return null; } // 2.根据beanId,从SpringBoot容器获取第一个handler对象 GatewayHandler firstGatewayHandler = SpringUtils.getBean(firstBeanHandlerId, GatewayHandler.class); if (firstGatewayHandler == null) { return null; } // 3. 获取下一个handlerBeanId String nextBeanHandlerId = firstGatewayHandlerEntity.getNextHandlerId(); // 定义临时循环遍历指针 GatewayHandler tempNextGatewayHandler = firstGatewayHandler; while (StringUtils.isNotEmpty(nextBeanHandlerId)) { // 4.根据beanId,从SpringBoot容器获取下一个handler对象 GatewayHandler nextGatewayHandler = SpringUtils.getBean(nextBeanHandlerId, GatewayHandler.class); if (nextGatewayHandler == null) { break; } // 5.从数据库查询该hanlder信息,从而获取下一个beanId GatewayHandlerEntity nextGatewayHandlerEntity = gatewayHandlerMapper.getByHandler(nextBeanHandlerId); if (nextGatewayHandlerEntity == null) { break; } // 6.设置下一个white循环遍历hanlderid nextBeanHandlerId = nextGatewayHandlerEntity.getNextHandlerId(); tempNextGatewayHandler.setNextGatewayHandler(nextGatewayHandler); tempNextGatewayHandler = nextGatewayHandler; } //设置只在启动时加载查询一次 this.firstGatewayHandler = firstGatewayHandler; return firstGatewayHandler; } }
相应,做一次测试:
/** * @author zhoumin * @create 2020-03-14 14:49 */ @RestController public class HandlerController { @Autowired private GatewayHandlerService gatewayHandlerService; @GetMapping("/clientHandler") public String clientHandler(){ ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler(); apiLimitHandler.service(); return "success!!"; } @GetMapping("/clientHandler2") public String clientHandler2(){ GatewayHandler gatewayHandler = gatewayHandlerService.getDbFirstGatewayHandler(); gatewayHandler.service(); return "success!!"; } }
运行后,数据如下:

如果想调节顺序,或者是新增节点,只需要修改数据库即可,相应,我们也可以把这块放到我们自己的管理后台管理。
在常用代码中,过滤器Filter就是这一模型很好的应用实例,具体可以看下:

doFilter即类似我们这里的nextService()方法。

完~
文中关于mysql的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《JAVA设计模式之责任链设计模式》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
499 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
101 收藏
-
443 收藏
-
202 收藏
-
365 收藏
-
223 收藏
-
334 收藏
-
224 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习