我在 Redant(https://github.com/all4you/redant) 中经过继承 ChannelHandler 实现了拦截器的功能,而且 pipeline 就是一种责任链模式的应用。可是后来我对本来的拦截器进行了从新设计,为何这样作呢,由于本来的方式是在 ChannelHandler 的基础上实现的,而咱们知道 Netty 的数据处理都是基于 ByteBuf 的,这就涉及到引用计数释放的问题,前面的 ChannelHandler 在处理时能够不关心引用计数的问题,而交给最后一个 ChannelHandler 去释放。前端
可是拦截器的一大特性就是当某个条件不知足时须要中断后面的操做直接返回,因此这就形成了在 pipeline 中某个节点须要释放引用计数,另一个方面就是原先的设计使用了不少自定义的 ChannelHandler,有的只作了一些简单的工做,因此彻底能够对他们进行合并,使代码变得更加精简紧凑。java
合并多个 ChannelHandler 是比较简单的,从新设计拦截器相对就复杂一些了。git
首先我把本来的前置拦截器和后置拦截器统一成一个拦截器,而后抽象出两个方法,分别表示:前置处理,后置处理,以下图所示:github
默认前置处理的方法返回 true,用户能够根据他们的业务进行覆盖。app
这里是定义了一个抽象类,也能够用接口,java 8 开始接口中能够有默认方法实现。ide
拦截器定义好以后,如今就能够在 ChannelHandler 中加入拦截器的方法调用了,以下图所示:post
当前置方法返回 false 时,直接返回,中断后面的业务逻辑处理,最终会到 finally 中将结果写入 response 中返回给前端。性能
如今只要实现 InterceptorHandler 中的两个方法就能够了,其实这也很简单,只要获取到全部的 Interceptor 的实现类,而后依次调用这些实现类的前置方法和后置方法就行了,以下图所示:测试
如今的重点就是怎样获取到全部的拦截器,首先能够想到的是经过扫描特定路径,找到全部 Interceptor 的实现类,而后将这些实现类加入到一个 List 中便可。ui
那怎么保证拦截器的执行顺序呢,很简单,只要在加入 List 以前对他们进行排序就能够了。定义一个 @Order 注解来表示排序的顺序,而后用一个 Wrapper 包装类将 Interceptor 和 Order 包装起来,排序到包装类的 List 中,最后再从包装类的 List 中依次取出全部的 Interceptor 就完成了 Interceptor 的排序了。
知道了大体的原理以后,实现起来就很简单了,以下图所示:
可是咱们不能每次都经过调用 scanInterceptors() 方法来获取全部的拦截器,若是这样每次都扫描一次的话性能会有影响,因此咱们只须要第一次调用一下该方法,而后把结果保存在一个私有的变量中,获取的时候直接读取该变量的值便可,以下图所示:
下面让咱们来自定义两个拦截器实现类,来验证下具体的效果。
第一个拦截器,在前置方法中对请求参数进行判断,若是请求参数中有 block=true 的参数,则进行拦截,以下图所示:
第二个拦截器,在后置方法中打印出每次请求的耗时,以下图所示:
经过 @Order 注解来指定执行的顺序,先执行 BlockInterceptor 再执行 PerformanceInterceptor。
如今咱们请求 /user/info 这个接口,查看下效果。
首先咱们只提交正常的参数,以下图所示:
打印的结果以下图所示:
从打印的结果中能够看到依次执行了:
这说明拦截器是按照 @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 和具体的实现类也要作响应的调整,以下图所示:
从新执行刚刚的测试类,发现结果和以前的同样,至此一个简单的链式责任链完成了。