责任链实战场景剖析、以及手写责任链

news2024/11/24 3:50:41

前言:

最早接触责任链这个设计模式,是我老早前看 Spring Aop 的源码的时候,Aop 的原理是遍历一根按照顺序装载好的 Advice(通知)拦截器链条,使@Before、@After 这些 Advice(通知)中的逻辑有顺序执行。如果我们没有 @After 的需要 ,Spring 只需把拦截器链条中的 @After 这个节点去掉就是了。十分的方便快捷!最近在看京东旗下的一个开源项目 hotkey 一个内存消息中间件,还有码云的一个 GVP 项目 austin 一个消息推送平台,里面用到了很多设计模式,不乏责任链的使用。当时看的时候一遍就过掉了,然后发现好像自己平时也没有总结过责任链这个设计模式,于是乎本文出现了。

责任链使用场景有哪些?

要知道责任链的使用场景有哪些就要先说下责任链的核心之处了:

  1. 责任链的节点可以任意组装
  2. 责任链的执行顺序可自定义

业务一:前置参数校验、接口逻辑执行、接口后置返回结果处理,这套业务就可以用责任链实现,还譬如业务二:审批流程:a 发起、b 抄送、c 审核、d 审核、e 审定。这种业务也可以用到责任链。只要是线性业务很多地方都可以用到责任链。假设业务一现在新增需求加入权限校验的功能,如果整体系统的接口请求都是走的业务一这套流程,加个权限校验无非就是在业务一责任链尾节点加个权限校验的节点就好了,其他业务逻辑根本不用变。如果没使用责任链的情况下是不是每个接口都需要做个判断(当前用户是否有权限访问该接口)。当然市面上有很多权限的框架、拦截器技术来实现等等,我这里只是介绍责任链设计模式的思想。就拿业务一来用责任链来实现的代码又是怎样的呢?请看下文

责任链链条组装时机

责任链无非就是按照顺序遍历执行其中的节点而已,那么责任链节点是什么时候塞进去的呢?常见的方式有俩种:一种是项目启动之初赋予默认节点、还有一种是用到的时候手动赋予节点。那么接下来就开始手撸代码。利用 @Resource 注解寻找 IOC 容器中的类型为(LoginInterceptor)的 bean 装配赋予 interceptors,可能有人会问为什么不用 @Autowired 呢?@Autowired 只适用于 Spring 环境,@Resource 除了适应 Spring 环境还可适应别的容器。由于我本地用的 Spring 这里用 @Resource、@Autowired 都行。另外还提供了一个 addInterceptors 方法,就是对应 interceptors 的 set 方法改了个名字而已,大家可以根据需要手动装配节点。

@Component
public class LoginInterceptorHandler {
    /**
     * 自动装配 ioc 容器中的类型为(LoginInterceptor)的 bean 链条 ,也可用 @Autowired 替代 
     */
    @Resource
    private List<ILoginInterceptor> interceptors;

    public Boolean chain(LoginContext loginContext) {
        for (ILoginInterceptor interceptor : interceptors) {
            Boolean doNext = interceptor.invoke(loginContext);
            //责任链某一环不执行了,后续节点都不会执行
            if (!doNext) return doNext;
        }
        return true;
    }

    /**
     * 提供扩展:手动配置生效的链条
     */
    public void addInterceptors(List<ILoginInterceptor> interceptors) {
        this.interceptors = interceptors;
    }
}

参数校验节点

由于责任链的节点是按某一类型的 Bean 进行装配的,但是每个节点的业务又都不一样,换句话说就是业务具有多态性,而责任链节点类型具有唯一性。因此我们定义一个 ILoginInterceptor 类型的接口,提供给每个节点实现。以下代码就是一个简单的参数校验节点。值得注意的是我这里用到了 @Order(1) 注解,值越小越先放到 IOC 容器,越先放到 IOC 中的越先被责任链装配,因此 @Order(1) 中的这个 1 代表责任链的头节点。

//值越小,在责任链中的排序越前
@Order(1)
@Slf4j
@Component
public class CheckParamInterceptor implements ILoginInterceptor {

    /**
     * 校验参数
     */
    @Override
    public Boolean invoke(LoginContext loginContext) {
        switch (loginContext.getType()) {
            //短信验证码
            case "1": {
                Assert.notBlank(loginContext.getCode(), "短信验证码不能为空");
                Assert.notBlank(loginContext.getUserName(), "用户名不能为空");
                Assert.notBlank(loginContext.getPassword(), "密码不能为空");
                if ("sucessCode".equals(loginContext.getCode())) throw new RuntimeException("短信验证码错误");
                break;
            }
            //用户名密码
            case "2": {
                Assert.notBlank(loginContext.getUserName(), "用户名不能为空");
                Assert.notBlank(loginContext.getPassword(), "密码不能为空");
                break;
            }
            //微信
            case "3": {
                Assert.notBlank(loginContext.getWxCode(), "微信授权码不能为空");
                break;
            }
            default:
                break;
        }
        return true;
    }
}

登录节点

//值越小,排序越前
@Order(2)
@Slf4j
@Component
public class DoLoginInterceptor implements ILoginInterceptor {
    /**
     * 真正的登录逻辑
     */
    @Override
    public Boolean invoke(LoginContext loginContext) {
        switch (loginContext.getType()) {
            //短信验证码
            case "1": {
                this.simpleLogin(loginContext);
                break;
            }
            //用户名密码
            case "2": {
                this.simpleLogin(loginContext);
                break;
            }
            //微信
            case "3": {
                this.wxLogin(loginContext);
                break;
            }
            default:
                break;
        }
        return true;
    }


    public void simpleLogin(LoginContext loginContext) {
        if (!("zzh".equals(loginContext.getUserName()) && "zzhpwd".equals(loginContext.getPassword())))
            throw new RuntimeException("用户名或者密码错误");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("uid", "21422");
        jsonObject.put("userName", loginContext.getUserName());
        jsonObject.put("password", loginContext.getPassword());
        loginContext.setUserInfo(jsonObject);
    }

    public void wxLogin(LoginContext loginContext) {
        //后续执行:通过微信 code 获取 token 进而获取微信用户信息,用户信息同步到本地的逻辑......
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("uid", "21422");
        jsonObject.put("userName", loginContext.getUserName());
        jsonObject.put("password", loginContext.getPassword());
        jsonObject.put("wxCode", loginContext.getWxCode());
        loginContext.setUserInfo(jsonObject);
    }
}

责任链触发时机

由于本文模拟的是登录逻辑,触发时机当然是在 Controller 中触发的了,注入责任链处理器,调用里面的 chain 方法即可。这样一来参数校验的业务、登录查 db 的业务就被我们组件化抽离开来了。

@Autowired
private LoginInterceptorHandler loginInterceptorHandler;

@PostMapping(value = "testchain")
public R testchain(@RequestBody LoginContext loginContext) {
    Boolean chain = loginInterceptorHandler.chain(loginContext);
    if (!chain) return R.failed("未知错误");
    //登录成功返回用户信息
    return R.ok(loginContext.getUserInfo());
}

这里提一嘴体外话:市面上有很多优秀的开源框架例如 Spring Aop 切面无非就是使用 JDK/CGLIB 代理+责任链实现的,而责任链里面装配了一个个 Advice 也就是我们常说的 @Before、@After 通知,当我们调用目标方法的时候会先执行我们的 @Before 增强逻辑,因为 @Before 增强逻辑节点排在了真正方法逻辑节点的前面。下面贴一段 Aop 责任链触发的源码,感兴趣的小伙伴可以阅读 aop增强器执行流程,手把手带你debug (文章末尾有干货)

而 hotkey 这个开源消息中间件是在 Netty 的 channelRead0 这个监听方法里面触发的责任链逻辑。具体怎么触发不重要根据各自的业务逻辑来即可。

在这里插入图片描述

责任链演示

参数错误是触发提示
在这里插入图片描述
正常登录
在这里插入图片描述

加入权限校验节点登录提示无权限

@Slf4j
@Order(3)
@Component
public class VerifyPermissionInterceptor implements ILoginInterceptor {
    @Override
    public Boolean invoke(LoginContext loginContext) {
        Object userInfo = loginContext.getUserInfo();
        //根绝 db 用户信息,解析对应的权限
        throw new RuntimeException("无权限访问系统");
    }
}

在这里插入图片描述

责任链总结

本文模拟的是比较简单也通用的责任链,可能很多小伙伴也看 Spring Aop的源码,发现里面的那个责任链和我本文写的不同,他那个写的比较复杂由于要兼顾到 @AfterThrowing 通知,他顺序遍历了一遍责任链后,又从责任链尾节点倒叙遍历了一遍责任链,而本文的这种只进行一遍顺序遍历责任链,也够应对日常开发了。具体 Spring Aop 是怎么遍历可以阅读 aop增强器执行流程,手把手带你debug (文章末尾有干货)

🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹读的是软件工程这个专业,内心却怀揣着一颗创作的心。对于敲代码而言我是业余的,但是写作我是认真的
🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹

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

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

相关文章

Accountill 使用 MongoDB、Express、React 和 Nodejs (MERN) 制作的全栈开源发票应用程序

Accountill 使用 MongoDB、Express、React 和 Nodejs (MERN) 制作的全栈开源发票应用程序。 介绍 使用 MERN 堆栈&#xff08;MongoDB、Express、React 和 Nodejs&#xff09;制作的全栈发票应用程序&#xff0c;专为自由职业者和小型企业设计&#xff0c;几乎可用于任何类型的…

职场中有哪些不成熟的表现

(点击即可收听) 大家好,这里是人人领读,今天给大家分享的,职场中有哪些不成熟的表现,希望能给大家带来一些启发. 1. 不主动汇报自己的工作进度 这个在职场当中,是非常忌讳的,一定要积极反馈,不能闷声憋着,说什么自己社恐,不敢跟上级领导交流,害怕被说 自己被分配的任务做到哪个…

仙人掌之歌——权力的游戏(4)

技术大培训 周一上午&#xff0c;陈速在工位上有些坐立不安&#xff0c;他也不知道自己在等待着什么。脑子里不可遏止地又想起上周五时&#xff0c;易伟成过来找自己说的那些没头没脑的话。易伟成先是询问直播串的参数细节&#xff0c;因为他要设计播放串加密方案&#xff0c;…

大数据架构系列:如何理解湖仓一体?

转载&#xff1a;如有侵权&#xff0c;告知即删除 引言 这十多年大数据技术蓬勃发展&#xff0c;从市场的表现来看基于大数据的数据存储和计算是非常有价值的&#xff0c;其中以云数据仓库为主打业务的公司Snowflake市值最高&#xff08;截止当前449亿美元&#xff09;&#x…

【Spring Cloud】演进与应用的分布式系统开发利器(文末赠书三本)

&#x1f338;作者简介&#xff1a;花想云&#xff0c;目前大二在读 &#xff0c;C/C领域新星创作者、运维领域新星创作者、CSDN2023新星计划导师、CSDN内容合伙人、阿里云专家博主、华为云云享专家 &#x1f338;专栏推荐&#xff1a;C语言初阶系列 、C语言进阶系列 、C系列、…

亚马逊测评养号系统是怎么操作的?

亚马逊鲲鹏测评养号系统可以注册亚马逊买家号、智能一键养号、批量绑定收货地址及支付卡、自动点击广告、货比三家后自动下单、自动留评、QA等&#xff0c;功能非常齐全&#xff0c;基本上是一款从注册到下单于一体的自动化软件。 具体操作流程是先准备好一批账号&#xff08;没…

《SIMD instruction considered harmful》SIMD指令被认为是有害的

作者&#xff1a;大卫帕特森 (David Patterson) 和安德鲁沃特曼 (Andrew Waterman)&#xff0c;2017 年 9 月 18 日 原文链接&#xff1a;SIMD Instructions Considered Harmful | SIGARCH 在撰写 《RISC-V 手册》的过程中&#xff0c;我们将 RISC-V 向量代码与 SIMD 进行了比…

创建一个 vue2.0 的项目(从0到1)的过程

1、首先&#xff1a;下载前端编码工具(如&#xff1a;VSCode) 2、其次&#xff1a;下载 node 与 npm 环境和管理&#xff1a; // 此时就说明成功安装 node 环境与 npm 管理工具&#xff1a; 3、然后&#xff1a;创建全局的 vue (如&#xff1a;vue2.0.8) 和 vue-cli 脚手架; …

Wiki.js 安装 linux(图解)

wiki.js是个开源的知识库系统&#xff0c;官方的docker安装总是出现各种问题&#xff0c; 官方也有给windows的安装包 wiki.js github 一、基础环境 1.nodejs环境 注意&#xff0c;wikijs2.0版本最高支持nodejs16 wikijs3.0支持nodejs18 参考这篇&#xff1a;nodejs编译安装…

Java爬虫通用模板它来了

Java 爬虫在实际应用中有很多场景&#xff0c;例如&#xff1a;数据挖掘和分析、搜索引擎、电商平台、数据更新、监控与预测等行业都需要爬虫借入&#xff0c;那么在实际爬虫中需要注意什么&#xff1f;又该怎么样快速实现爬虫&#xff1f;下面的文章值得看一看。 单线程java爬…

精准监测 智慧气象数据可视化大屏监测系统

前言 风云变幻&#xff0c;气象先行。天气、气候和水对公众的福祉、健康和粮食安全至关重要。 建设背景 市场背景 在全球气候变暖背景下&#xff0c;我国极端天气气候事件明显增多、强度明显增强&#xff0c;气候复杂多变&#xff0c;台风、暴雨、冰雹等灾害多发。常给人民…

网易NDH基于Impala的高性能SQL引擎建设实践

导读&#xff1a;本文将从四个方面来进行介绍。首先是分析在网易NDH中使用 Impala 过程遇到的一些痛点&#xff1b;第二个部分是基于这些痛点问题&#xff0c;我们提出了建设高性能SQL引擎的方案&#xff0c;以及这些方案是基于什么原则来创建的&#xff1b;第三个是基于这些原…

Qt使用第三方库openssl进行RSA加密解密操作详解

一、openssl库的编译,可以参考文档: https://blog.csdn.net/liang19890820/article/details/51658574/ 因为我这里使用的是windows操作系统,可以直接下载exe格式的安装文件,直接安装即可,就包含了我们需要的头文件和库文件,省去了编译操作。exe安装文件下载地址: htt…

Matlab中求解线性方程组——高斯消元法、LU分解法、QR分解法、SVD分解法、迭代法等

系列文章目录 MATLAB迭代的三种方式以及相关案例举例 MATLAB矩阵的分解函数与案例举例 MATLAB当中线性方程组、不定方程组、奇异方程组、超定方程组的介绍 MATLAB语句实现方阵性质的验证 MATLAB绘图函数的相关介绍——海底测量、二维与三维图形绘制 MATLAB求函数极限的简…

文件系统考古2:1984 - BSD Fast Filing System

今天继续与大家分享系列文章《50 years in filesystems》&#xff0c;由 KRISTIAN KHNTOPP 撰写。 我们将进入文件系统的第二个十年&#xff0c;即1984年&#xff0c;计算机由微型计算机发展到了桌面和机柜工作站&#xff0c; BSD Fast Filing System 登场。 回看第一篇&…

浅谈开源与闭源

开源指的是那些源代码或源设计可以被大众使用、修改发行的软件或设计体&#xff0c;闭源就是不开放源代码。 Linux 无疑是开源软件里最最成功的一个&#xff0c;不管是从它目前的生态建设角度&#xff0c;还是从业界评价来看&#xff0c;包括今天云计算的基础也都倚赖Linux的贡…

【SQL Server】数据库开发指南(九)详细讲解 MS-SQL 触发器的基础概念与应用场景

本系列博文还在更新中&#xff0c;收录在专栏&#xff1a;#MS-SQL Server 专栏中。 本系列文章列表如下&#xff1a; 【SQL Server】 Linux 运维下对 SQL Server 进行安装、升级、回滚、卸载操作 【SQL Server】数据库开发指南&#xff08;一&#xff09;数据库设计的核心概念…

WIN10:Cognos10.2_x32安装运行问题总结(二)

一、Cognos安装Congfiguration启动服务异常解决方法 1、用管理员权限启动IBM Cognos Configuration、关闭本机安全软件、关闭防火墙&#xff1b; 2、不卸载本机JDK不删除本机JDK环境&#xff0c;指定c10\bin目录&#xff0c;cogconfig -java:local 命令CMD启动Cognos。 测试…

JavaSE07_static和final

JavaSE-07 【static-final】 第一章 static关键字 1.1 静态static关键字概述 static关键字 一个类的不同对象有些共享的数据&#xff0c;这样我们就可以使用static来修饰 。一旦使用了static关键字&#xff0c;那么这样的内容不再属于对象&#xff0c;而是属于类的&#xff…

通过python封装接口seller_nick获取阿里巴巴店铺所有商品数据,阿里巴巴店铺所有商品数据接口,阿里巴巴API接口

今天给大家分享一个大体的思路和代码示例。请确保采集过程遵循相关网站的使用规则和政策。 使用阿里巴巴开放平台提供的API接口文档&#xff0c;找到seller_nick接口的具体参数及请求方式。使用ython中的requests库发送请求&#xff0c;获取接口返回的数据。对返回的数据进行处…