我在 Redant(https://github.com/all4you/redant) 中通过继承 ChannelHandler 实现了拦截器的功能,并且 pipeline 就是一种责任链模式的应用。但是我后面对原本的拦截器进行了重新设计,为什么这样做呢,因为原本的方式是在 ChannelHandler 的基础上操作的,而我们知道 Netty 的数据处理都是基于 ByteBuf 的,这就涉及到引用计数释放的问题,前面的 ChannelHandler 在处理时可以不关心引用计数的问题,而交给最后一个 ChannelHandler 去释放。

但是拦截器的一大特性就是当某个条件不满足时需要中断后面的操作直接返回,所以这就造成了在 pipeline 中某个节点需要释放引用计数,另外一个方面就是原先的设计使用了很多自定义的 ChannelHandler,有的只做了一些简单的工作,所以完全可以对他们进行合并,使代码变得更加精简紧凑。

合并多个 ChannelHandler 是比较简单的,重新设计拦截器相对就复杂一些了。

重新设计拦截器

首先我把原本的前置拦截器和后置拦截器统一成一个拦截器,然后抽象出两个方法,分别表示:前置处理,后置处理,如下图所示:

默认前置处理的方法返回 true,用户可以根据他们的业务进行覆盖。

这里是定义了一个抽象类,也可以用接口,java 8 开始接口中可以有默认方法实现。

拦截器定义好之后,现在就可以在 ChannelHandler 中加入拦截器的方法调用了,如下图所示:

当前置方法返回 false 时,直接返回,中断后面的业务逻辑处理,最终会到 finally 中将结果写入 response 中返回给前端。

现在只要实现 InterceptorHandler 中的两个方法就可以了,其实这也很简单,只要获取到所有的 Interceptor 的实现类,然后依次调用这些实现类的前置方法和后置方法就好了,如下图所示:

获取拦截器

现在的重点就是怎样获取到所有的拦截器,首先可以想到的是通过扫描的方法,找到所有 Interceptor 的实现类,然后将这些实现类加入到一个 List 中即可。

那怎么保证拦截器的执行顺序呢,很简单,只要在加入 List 之前对他们进行排序就可以了。再定义一个 @Order 注解来表示排序的顺序,然后用一个 Wrapper 包装类将 Interceptor 和 Order 包装起来,排序到包装类的 List 中,最后再从包装类的 List 中依次取出所有的 Interceptor 就完成了 Interceptor 的排序了。

知道了大致的原理之后,实现起来就很简单了,如下图所示:

但是我们不能每次都通过调用 scanInterceptors() 方法来获取所有的拦截器,如果这样每次都扫描一次的话性能会有影响,所以我们只需要第一次调用一下该方法,然后把结果保存在一个私有的变量中,获取的时候直接读取该变量的值即可,如下图所示:

自定义拦截器实现类

下面让我们来自定义两个拦截器实现类,来验证下具体的效果。

第一个拦截器,在前置方法中对请求参数进行判断,如果请求参数中有 block=true 的参数,则进行拦截,如下图所示:

第二个拦截器,在后置方法中打印出每次请求的耗时,如下图所示:

通过 @Order 注解来指定执行的顺序,先执行 BlockInterceptor 再执行 PerformanceInterceptor。

查看效果

现在我们请求 /user/info 这个接口,查看下效果。

首先我们只提交正常的参数,如下图所示:

打印的结果如下图所示:

从打印的结果中可以看到依次执行了:

  • BlockInterceptor 的 preHandle 方法

  • PerformanceInterceptor 的 preHandle方法

  • BlockInterceptor 的 postHandle 方法

  • PerformanceInterceptor 的 postHandle方法

这说明拦截器是按照 @Order 注解进行了排序,然后依次执行的。

然后我们再提交一个 block=true 的参数,再次请求该接口,如下图所示:

可以看到该请求已经被拦截器的前置方法给拦截了,再看下打印的日志,如下图所示:

只打印了 BlockInterceptor 的 preHandler 方法中的部分日志,后面的方法都没有执行,因为被拦截了直接返回了。

存在的问题

到这里已经对拦截器完成了改造,并且也验证了效果,看上去效果还可以。但是有没有什么问题呢?

还真有一个问题:所有的 Interceptor 实现类只要被扫描到了,就会被加入到 List 中去,如果不想应用某一个拦截器这时就做不到了,因为无法对 list 中的值进行动态的更改。

如果我们可以动态的获取一个保存了 Interceptor 的 list ,如果该 list 中没有获取到值,再通过扫描的方式去拿到所有的 Interceptor 这样就完美了。

动态获取 Interceptor 的 list 的方法,可以由用户自定义实现,根据某些规则来确定要不要将某个 Interceptor 加入到 list 中去,这样就把 Interceptor 的实现和使用进行了解耦了。用户可以实现任意多的 Interceptor,但是只根据规则去使用其中的某些 Interceptor。

理清楚了原理之后,就很好实现了,首先定义一个接口,用来构造 Interceptor 的 List,如下图所示:

有了 InterceptorBuilder 之后,在获取 Interceptor 的时候,就可以先根据 InterceptorBuilder 来获取了,如下图所示:

以下是一个示例的 InterceptorBuilder,具体的可以用户自行扩展设计,如下图所示:

这样用户只要实现一个 InterceptorBuilder 接口,即可按照自己的意图去组装所有的拦截器。

链式责任链

在 Redant 中实现的拦截器所使用的责任链,其实是通过一个 List 来保存了所有的 Interceptor,那我们通常所说的责任链除了使用 List 来实现外,还可以通过真正的链表结构来实现,Netty 和 Sentinel 中都有这样的实现,下面我来实现一个简单的链式结构的责任链。

责任链的应用已经有很多了,这里不再赘述,假设我们需要对前端提交的请求做以下操作:鉴权,登录,日志记录,通过责任链来做这些处理是非常合适的。

首先定义一个处理接口,如下图所示:

通过 List 方式的实现很简单,只需要把每个 Processor 的实现类添加到一个 List 中即可,处理的时候遍历该 List 依次处理,这里不再做具体的描述,感兴趣的可以自行实现。

定义节点

如果是通过链表的形式来实现的话,首先我们需要有一个类表示链表中的某个节点,并且该节点需要有一个同类型的私有变量表示该节点的下个节点,这样就可以实现一个链表了,如下图所示:

定义容器

接着我们需要定义一个容器,在容器中有头,尾两个节点,头结点作为一个空节点,真正的节点将添加到头结点的 next 节点上去,尾节点作为一个指针,用来指向当前添加的节点,下一次添加新节点时,将从尾节点处添加。有了具体的处理逻辑之后,实现起来就很简单了,这个容器的实现如下图所示:

定义实现类

下面我们可以实现具体的 Processor 来处理业务逻辑了,只要继承 AbstractLinkedProcessor 即可,如下图所示:

其他两个实现类: LoginProcessor ,LogProcessor 类似,这里就不贴出来了。

然后就可以根据规则来组装所需要的 Processor 了,假设我们的规则是需要对请求依次进行:鉴权,登录,日志记录,那组装的代码如下图所示:

执行该代码,结果如下图所示:

存在的问题

看的仔细的同学可能发现了,在 AuthProcessor 的业务逻辑实现中,除了执行了具体的逻辑代码之外,还调用了一行 super.process(content) 代码,这行代码的作用是调用链表中的下一个节点的 process 方法。但是如果有一天我们在写自己的 Processor 实现类时,忘记调用这行代码的话,会是怎样的结果呢?

结果就是当前节点后面的节点不会被调用,整个链表就像断掉一样,那怎样来避免这种问题的发生呢?其实我们在 AbstractProcessor 中已经实现了 process 方法,该方法就是调用下个节点的 process 方法的。那我们在这个方法触发调用下个节点之前,再抽象出一个用以具体的业务逻辑处理的方法 doProcess ,先执行 doProcess 方法,执行完之后再触发下个节点的 process ,这样就不会出现链表断掉的情况了,具体的实现如下图所示:

相应的 LinkedProcessorChain 和具体的实现类也要做响应的调整,如下图所示:

重新执行刚刚的测试类,发现结果和之前的一样,至此一个简单的链式责任链完成了。

关注「逅弈逐码」

查看更多原创好文

更多推荐内容

↓↓↓

《Sentinel 原理:控制台是如何获取到实时数据的》

《Sentinel 实战:搭建集群限流环境》

《Netty 实战:如何编写一个麻小俱全的 Web 容器》

《让人欢喜让人忧的正则表达式》

wrapper怎么用_用责任链模式设计拦截器相关推荐

  1. 【设计模式】第五章 责任链模式

    第五章 责任链模式 文章目录 第五章 责任链模式 一.介绍 二.结构 三.实现 一.介绍 在现实生活中,一个事件需要经过多个对象处理是很常见的场景.例如,采购审批流程.请假流程等.公司员工请假,可批假 ...

  2. 设计模式学习笔记(六:责任链模式)

    1.1概述 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.这就是责任链模式. 责任链模式是使用多个对象 ...

  3. 责任链模式实践之Zuul责任链模式

    责任链模式实践之Zuul责任链模式 一,什么是责任链模式 责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对 ...

  4. Java设计模式——责任链模式(职责链模式)详解

    模式的定义与特点 责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链:当 ...

  5. 责任链模式(职责链模式)详解——小马同学@Tian

    责任链模式(职责链模式)详解 在现实生活中,一个事件需要经过多个对象处理是很常见的场景.例如,采购审批流程.请假流程等.公司员工请假,可批假的领导有部门负责人.副总经理.总经理等,但每个领导能批准的天 ...

  6. 责任链模式(职责链模式)详解

    在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同.例如,公司员工请假,可批假的领导有部门负责人.副总经理.总经理等,但每个领导能批准的天数不同,员工必须根 ...

  7. java语言描述一个行为_设计模式之责任链模式——Java语言描述

    责任链模式为请求创建了一个接受者对象的链.这种模式给予请求的类型,对请求的发送者和接受者进行解耦.这种类型的设计模式属于行为模式.在这种模式下,通常每个接收者都包含对另一个接收者的引用.如果一个对象不 ...

  8. java实现责任链模式_我的Java设计模式-责任链模式

    今天来说说程序员小猿和产品就关于需求发生的故事.前不久,小猿收到了产品的需求. 产品经理:小猿,为了迎合大众屌丝用户的口味,我们要放一张图,要露点的. 小猿:......露点?你大爷的,让身为正义与纯 ...

  9. java 责任链模式 链表_责任链模式的实现及源码中应用

    01 - 责任链模式的实现 假设一个出差任务的流程需要审批出差行程和出差报销金额.那么,对应两个部门的审核.我们先定义一个出差任务Task类: 然后,我们定义一个抽象的处理类Handler,其中具体的 ...

最新文章

  1. ExpressJs server中Router的设置
  2. JDBC , 使用java来控制mysql。JavaWeb开发的分层设计-三层架:DAO层设计,连接池使用,类加载使用,配置...
  3. mysql连接池 golang_Golang 你一定要懂的连接池
  4. LeetCode:Rotate Image
  5. JAVA 面向对象的一些基础理解
  6. 第五章 处理器拦截器详解
  7. JAVAFX-1 开发应用
  8. 树莓派 armv几_如何在具有armv6处理器的树莓派板上安装和使用Java 11和JavaFX 11
  9. zint.dll 二维码、条形码库的获取及简单使用
  10. EndNote保姆级快速上手使用教程,小白必看!
  11. 01.电脑黑客基本知识
  12. VisualStudio更改项目名字
  13. three.js 相对坐标的设置
  14. 安徽大学电气工程及自动化学院本科实验报告(MATLAB)——六、七章
  15. 聊聊Linux中的线程本地存储(1)——什么是TLS
  16. Bootstrap进阶四:jQuery插件详解
  17. 【论文简述】EPP-MVSNet: Epipolar-assembling based Depth Prediction for Multi-view Stereo(ICCV 2021)
  18. 何为数字化工厂?如何打造真正的数字化工厂?
  19. CIO与CISO的角色演变
  20. CF刷题-Codeforces Round #481-F. Mentors

热门文章

  1. With you With me
  2. Algorithm-Gossip(4) 三色棋(Three_Color_Flag)
  3. 用Keil-MDK开发TQ2440裸机程序入门教程——LED流水灯实现
  4. elasticsearch-1
  5. CentOS 7 安装并启动Nginx
  6. 易语言mysql锁表_MySQL的3种锁定机制
  7. python读取图像矩阵文件并转换为向量
  8. 数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF
  9. C++建立队列_利用链表
  10. Linux开发cocos2dx程序环境搭建