开源框架中的责任链模式实践
作者:vivo 互联网服务器团队-Wang Zhi
责任链模式作为常用的设计模式而被大家熟知和使用。本文介绍责任链的常见实现方式,并结合开源框架如 Dubbo、Sentinel 等进行延伸探讨。
一、责任链介绍
在 GoF 的《设计模式》一书中对责任链模定义的:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止或者所有接收对象处理一遍。
用通俗的话解释在责任链模式中,多个处理器(接收对象)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。
责任链模式有效地降低了发送和接收者之间的耦合度,增强了系统的可扩展性。在责任链的模式下不仅能够针对单个处理器对象进行定制升级(每个处理器对象关注各自的任务),而且能够对整个责任链的处理器对象的顺序的调整以及增删。
本文约定:责任链上的接收对象统一称为处理器;本文中介绍的责任链属于 GOF 定义中责任链的变种即责任链上的所有处理器都会参与任务的处理。
二、责任链实现
责任链模式有多种实现方式,从驱动责任链上处理器方式的角度可以分类两类,即责任链驱动 和 责任链处理器自驱动。
2.1 处理器自驱动
说明:
责任链上的每个处理器对象维护下一个处理器对象,整个责任链的驱动由每个处理器对象自行驱动。
每个处理器对象 Handler 中包含下一个处理器对象 next 的变量,通过链表形式维护责任链的关系。
2.2 责任链驱动
说明:
责任链对象本身以数组的形式维护处理器对象,即上述代码中的 handlers 。
责任链的处理器的执行由责任链对象循环调用处理器对象驱动,即上述代码中的 handle 方法。
三、开源框架中责任链应用
责任链低耦合高扩展的特点让它在很多开源的框架中被采用,本文选取了开源框架中的 Spring Interceptor、Servlet Filter、Dubbo、Sentinel 进行责任链的实现介绍,通过对常用框架中责任链应用的了解能够更好掌握责任链落地并在日常的开发中积极的使用。
3.1 Spring Interceptor
3.1.1 Interceptor 介绍
Spring 中的拦截器(Interceptor) 用于拦截控制器方法的执行,可以在方法执行前后添加自定义逻辑类似于 AOP 编程思想。
Inteceptor 的作用时机是在请求(request)进入 servlet 后,在进入 Controller 之前进行预处理。
Inteceptor 的实际应用包括:认证授权、日志记录、字符编码转换,敏感词过滤等等。
Inteceptor 中责任链的实现会从处理器的介绍,责任链的构建以及责任链的执行三个角度进行阐述。
3.1.2 处理器介绍
说明:
处理器 Interceptor 的接口 HandlerInterceptor 定义了三个方法,可在控制器方法执行前后添加自定义逻辑。
自定义处理器如上的 TimeInterceptor 需要自定义实现上述 3 个方法实现自我的逻辑。
所有的自定义处理会串联在 HandlerExecutionChain 类实现的责任链上。
3.1.3 责任链构建
说明:
HandlerExecutionChain 类作为串联 Interceptor 处理器的责任链负责责任链的构建和执行。
HandlerExecutionChain 类通过集合对象 interceptorList 保存所有相关的处理器对象。
3.1.4 责任链执行
说明:
在 servlet 的 doDispatch 方法中依次触发责任链的 applyPreHandle 的前置处理方法、applyPostHandle 的后置处理方法。
前置处理方法 applyPreHandle 会遍历责任链上的处理器从前往后依次处理,后置处理方法 applyPostHandle 会遍历责任链上的处理器从后往前依次处理。
处理器的驱动由责任链对象负责依次触发,非处理器对象自驱执行。
3.2 Servlet Filter
3.2.1 Filter 介绍
Servlet 过滤器是在 Java Servlet 规范 2.3 中定义的,它能够对 Servlet 容器的请求和响应对象进行检查和修改,是个典型的责任链。
在 Servlet 被调用之前检查 Request 对象并支持修改 Request Header 和 Request 内容。
在 Servlet 被调用之后检查 Response 对象并支修改 Response Header 和 Response 内容。
3.2.2 处理器介绍
说明:
Servlet 过滤器类要实现 javax.servlet.Filter 接口,该接口定义了通用的 3 个方法。
init 方法:负责 Servlet 过滤器的初始化方法,Servlet 容器创建 Servlet 过滤器实例过程中调用这个方法。
doFilter 方法:当客户请求访问与过滤器关联的 URL 时,Servlet 容器会调用该方法。
destroy 方法:Servlet 容器在销毁过滤器实例前调用该方法,可以释放过滤器占用的资源。
3.2.3 责任链构建
说明:
ApplicationFilterChain 作为 Filter 的责任链,负责责任链的构建和执行。
责任链通过 ApplicationFilterConfig 类型的数组对象 filters 保存 Filter 处理器。
责任链上处理器的添加通过保存到数组 filters 来实现。
3.2.4 责任链执行
说明:
整个责任链上 Filter 处理器的执行通过处理器自驱进行实现,而非由责任链对象驱动。
Filter 处理器的在处理过程中除了执行自我逻辑,会通过 filterChain.doFilter(servletRequest, servletResponse) 触发下一个处理器的执行。
3.3 Dubbo
3.3.1 Dubbo Filter 介绍
图片分享自《DUBBO官网》
Dubbo 的 Filter 作用时机如上图所示,Filter 实现是专门为服务提供方和服务消费方调用过程进行拦截,Dubbo 本身的大多功能均基于此扩展点实现,每次远程方法执行该拦截都会被执行。
Dubbo 官方针对 Filter 做了很多的原生支持,目前大致有 20 来个吧,包括我们熟知的 RpcContext,accesslog 功能都是通过 filter 来实现了。
在实际业务开发中会对 Filter 接口进行扩展,在服务调用链路中嵌入我们自身的处理逻辑,如日志打印、调用耗时统计等。
3.3.2 处理器介绍
说明:
Dubbo 中的自定义 Filter 需要实现 org.apache.dubbo.rpc.Filter 类,内部通过实现 invoke 方法来实现自定义逻辑。
自定义 Filter 内部除了实现必要的自定义逻辑外,核心的需要通过 invoker.invoke(inv)触发下一个过滤器的执行。
3.3.3 责任链构建
说明:
ProtocolFilterWrapper 通过 buildInvokerChain 构建 Dubbo Filter 的责任链。
责任链上的处理器对象是将 Filter 封装的 Invoker 对象,每个 Invoker 对象指向下一个处理器封装的 Invoker 对象。
3.3.4 责任链执行
说明:
每个 Invoker 对象 invoke 方法会执行自定义逻辑,并触发下一个处理器的执行。
整个责任链上处理器的执行通过 Invoker 对象的驱动,而非责任链对象的驱动。
3.4 Sentinel
3.4.1 Sentinel Slot 介绍
图片分享自《Sentinel官网》
Sentinel 是面向分布式服务架构的流量治理组件,以流量为切入点提供熔断限流的功能保证系统的稳定性。
Sentinel 里面以 Entry 作为限流的资源对象,每个 Entry 创建的同时会关联一系列功能插槽(slot chain)。
Sentinel 提供了通用的原生 Slot 处理不同的逻辑,同时支持自定义 Slot 来定制功能。
3.4.2 处理器介绍
说明:
Sentinel 中的 Slot 需要实现 com.alibaba.csp.sentinel.slotchain.ProcessorSlot 的通用接口。
自定义 Slot 一般继承抽象类 AbstractLinkedProcessorSlot 且只要改写 entry/exit 方法实现自定义逻辑。
Slot 通过 next 变量保存下一个处理器 Slot 对象。
在自定义实现的 entry 方法中需要通过 fireEntry 触发下一个处理器的执行,在 exit 方法中通过 fireExit 触发下一个处理器的执行。
3.4.3 责任链构建
说明:
ProcessorSlotChain 作为 Slot 的责任链,负责责任链的构建和执行。
责任链上的处理器对象 AbstractLinkedProcessorSlot 通过保存指向下一个处理器的对象的进行关联,整体以链表的形式进行串联。
责任链上的第一个处理器对象 first 本身不起任何作用,只是保存链表的头部。
3.4.4 责任链执行
说明:
整个责任链上处理器的执行通过 Invoker 对象的驱动,而非责任链对象的驱动。
DefaultProcessorSlotChain 的 entry 首先头部对象 first,进而触发处理器的自驱实现处理器的执行。
整体按照 entry → fireEntry → transformEntry → entry 的循环顺序依次触发处理器的自驱。
四、实践总结
在日常项目实践中,责任链的设计模式会在很多业务场景中落地。
譬如对于支持用户生成内容(UGC)的应用来说,用户生成的内容可能包含一些敏感内容如敏感言论或者图片等。针对这种应用场景,可以通过责任链模式设置多个处理器来处理不同的任务,如文本过滤器处理敏感词,图片过滤器处理敏感图片等等。
譬如对于电商服务中的下单流程来说,一个下单流程包含订单拆合单,优惠计算,订单生成等多个步骤,我们可以通过责任链模式设置多个处理器来处理不同的任务等等。
责任链的应用场景非常广泛,在常见的开源框架中有丰富的落地场景,同样在业务开发中也可以根据场景灵活使用。
版权声明: 本文为 InfoQ 作者【vivo互联网技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/d8fd3b66c0e7d531d933d3264】。文章转载请联系作者。
评论