责任链模式在 Spring 中的应用
前言
最近工作中有个业务场景非常适合使用责任链模式,且有好几个地方都能使用到。为了写一个比较通用且完善的责任链,阅读了 Spring 框架中一些责任链的实现作为参考。
Spring 中责任链模式的应用
责任链的应用非常广泛,在 Spring 中都有很多应用,这里分析两个非常常用的功能是如何实现的。
Spring Web 中的 HandlerInterceptor
HandlerInterceptor
接口在web开发中非常常用,里面有preHandle()
、postHandle()
、afterCompletion()
三个方法,实现这三个方法可以分别在调用"Controller"方法之前,调用"Controller"方法之后渲染"ModelAndView"之前,以及渲染"ModelAndView"之后执行。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {return true;
}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}}
HandlerInterceptor
在责任链中充当处理者的角色,通过HandlerExecutionChain
进行责任链调用。public class HandlerExecutionChain {...@Nullable
private HandlerInterceptor[] interceptors;
private int interceptorIndex = -1;
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0;
i < interceptors.length;
i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1;
i >= 0;
i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex;
i >= 0;
i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}
调用的方式非常简单,即通过数组存储注册在Spring中的
HandlerInterceptor
,然后通过interceptorIndex
作为指针去遍历责任链数组按顺序调用处理者。文章图片
Spring AOP Spring AOP实现的更加灵活,可以实现前置(
@Before
)、后置(@After
)、环绕(@Around
)等多种切入时机。目前Spring AOP 的动态代理有两种实现:JdkDynamicAopProxy
和CglibAopProxy
。这里就以CglibAopProxy
的实现流程看一下责任链的使用。首先Spring会根据配置等决定对一个对象进行代理,
JdkDynamicAopProxy
实现了InvocationHandler
接口并实现invoke()
方法。熟悉JDK动态代理的都知道通过代理对象调用方法时,会进入到InvocationHandler
对象的invoke()
方法,所以我们直接从JdkDynamicAopProxy
的这个方法开始:final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
try {
// 1. 校验及跳过一些无需代理的方法
... // 2. 获取目标方法所有的拦截器
List
这个方法整体流程比较长的,我只展示了和责任链有关的部分。步骤2通过获取和目标代理方法相关的所有
Advice
,Advice
可以说是拦截器在Spring中的封装,即我们编写的AOP方法的封装。接着如果有拦截器则通过步骤3生成一个ReflectiveMethodInvocation
,并且执行该责任链。再看看
ReflectiveMethodInvocation
的proceed()
方法是怎么样的:public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {protected ReflectiveMethodInvocation(
Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
@Nullable Class> targetClass, List
- 首先
ReflectiveMethodInvocation
在构造方法中保存JdkDynamicAopProxy
传入的拦截器列表(chain
)到interceptorsAndDynamicMethodMatchers
。
- 接着在
proceed()
方法中,先判定当前currentInterceptorIndex
指针是否到拦截器列表size,如果到了说明拦截器都执行过了,就去调用目标代理方法。 - 否则获取当前拦截器,通常类型为
InterceptorAndDynamicMethodMatcher
,InterceptorAndDynamicMethodMatcher
里面只有两个属性,MethodMatcher
和MethodInterceptor
,前者用于判定拦截器是否需要匹配目标代理方法,后者就是拦截器本身。 - 如果当前
InterceptorAndDynamicMethodMatcher
匹配目标代理方法,则调用当前拦截器,否则直接再次调用当前proceed()
形成递归。
ReflectiveMethodInvocation
就是责任链中的“链条”,处理者是InterceptorAndDynamicMethodMatcher
,更准确的说是InterceptorAndDynamicMethodMatcher
里的MethodInterceptor
实现类。这里的MethodInterceptor
在Spring中被封装成各种Advice
,例如环绕(@Around
)的切面会被封装成AspectJAroundAdvice
。AspectJAroundAdvice
的详细代码先不看,先写一个最基本的AOP切面@Slf4j
@Aspect
@Component
public class LogAop {
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void logPointCut() {
}@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
log.info("LogAop start");
Object result = point.proceed();
log.info("LogAop end");
return result;
}
}
@Pointcut
里的匹配规则会被封装到InterceptorAndDynamicMethodMatcher
的MethodMatcher
作为匹配规则,而@Around
注解的方法就会被封装到MethodInterceptor
,这里即是AspectJAroundAdvice
。在AspectJAroundAdvice
里会封装ProceedingJoinPoint
等参数,然后作为入参反射调用对应的AOP方法。public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
// 1. args AOP方法的入参,通常为ProceedingJoinPoint
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// 2. this.aspectInstanceFactory.getAspectInstance()获取AOP切面的实例
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("Mismatch on arguments to advice method [" +
this.aspectJAdviceMethod + "];
pointcut expression [" +
this.pointcut.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
入参
ProceedingJoinPoint
,里面包含了目标代理方法的一些信息,更重要的是我们会调用该对象的proceed()
方法来尝试调用目标代理方法。即我们可以在AOP切面调用proceed()
方法前后编写我们期望在目标代理方法前后要执行的代码。如在
LogAop
里会在目标代理方法执行前打印LogAop start
,执行后打印LogAop end
。ProceedingJoinPoint.proceed()
又是怎么做的呢,这里看下Spring的实现类MethodInvocationProceedingJoinPoint
的代码。public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint, JoinPoint.StaticPart {
@Override
public Object proceed() throws Throwable {
return this.methodInvocation.invocableClone().proceed();
}
}
很简单的一段代码,克隆
methodInvocation
并调用其proceed()
方法。这里的methodInvocation
就是一开始提到的ReflectiveMethodInvocation
,所以绕了一大圈,实际上形成了ReflectiveMethodInvocation.proceed()
的递归。文章图片
![]()
两种实现总结 Spring Web 中的
HandlerInterceptor
采用数组的顺序遍历模式来控制责任链链条的推动,这种模式可以让处理者无需手动控制链条,每个处理者之间也不会相互收到干扰。但是同时处理者就无法打断责任链,必须处理完所有的处理者才会结束。且处理者的调用时机也是由链来决定,相对没那么灵活。Spring AOP则采用递归的方式,处理者要在自己的功能里显示的调用来推动链条,这样相对比较灵活,可以自己决定推进时机,甚至可以打断责任链。这样的话对其他处理者来说有点不可控,有时责任链被其他处理者打断而导致自己未被调用到,增加了调试的困难。
这两种责任链的链条都使用
Index
变量来控制链条推动,这就意味着无法“共享”一个链条,每次使用责任链都要重新生成,用完又需要销毁,比较消耗处理器资源。设计通用责任链
在前面学习了其他责任链的实现,现在自己实现一个通用的的责任链。
先写一个基础的处理器接口:
/**
* 通用责任链的 Handler
* * 每个业务声明一个该接口的子接口或者抽象类,再基于该接口实现对应的业务 Handler。
* 这样 BaseHandlerChain 可以直接注入到对应的 Handler List
*/
public interface BaseHandler {@NonNull
HandleResult doHandle(Param param);
default boolean isHandler(Param param) {
return true;
}
}
处理器接口有两个方法,
isHandler()
用于判定该处理器是否需要执行,默认为true
。doHandle()
方法执行处理器逻辑,且返回HandleResult
用于返回处理结果,并判定是否继续执行下一个处理器。@Getter
public class HandleResult {
private final R data;
private final boolean next;
private HandleResult(R r, boolean next) {
this.data = https://www.it610.com/article/r;
this.next = next;
}public static HandleResult doNextResult() {
return new HandleResult<>(null, true);
}public static HandleResult doCurrentResult(R r) {
return new HandleResult<>(r, false);
}
}
最后编写责任链控制器的代码:
/**
* 通用责任链模式
* * 使用方法:
* * 1. 创建一个对应业务的责任链控制器, 继承 BaseHandlerChain,
* 如: {@code MyHandlerChain extends BaseHandlerChain}
* * 2. 创建一个对应业务的责任链处理器 Handler,继承 BaseHandler,
* 如: {@code MyHandler extends BaseHandler}
* * 3. 编写业务需要的处理器 Handler 实现 MyHandler 接口的 doHandle 方法。推荐把控制器和处理器都交给 Spring 控制,可以直接注入。
*/
public class BaseHandlerChain, Param, Result> {@Getter
private final List handlerList;
public BaseHandlerChain(List handlerList) {
this.handlerList = handlerList;
}public Result handleChain(Param param) {
for (Handler handler : handlerList) {
if (!handler.isHandler(param)) {
continue;
}
HandleResult result = handler.doHandle(param);
if (result.isNext()) {
continue;
}
return result.getData();
}return null;
}
}
这样就责任链就完成了,现在来做个简单的使用演示
/**
* 基于业务的基础处理器接口
*/
public interface MyHandler extends BaseHandler {
}/**
* 基于业务的控制器接口
*/
@Service
public class MyHandlerChain extends BaseHandlerChain {@Autowired
public MyHandlerChain(List myHandlers) {
super(myHandlers);
}
}/**
* 处理器1
*/
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyLogHandler implements MyHandler {@Override
public @NonNull HandleResult doHandle(String param) {
log.info("MyLogHandler hello {} !", param);
return HandleResult.doNextResult();
}
}/**
* 处理器2
*/
@Slf4j
@Component
public class MyDefaultHandler implements MyHandler {@Override
public @NonNull HandleResult doHandle(String param) {
log.info("param is {}", param);
return HandleResult.doCurrentResult("MyDefaultHandler");
}
}/**
* 单元测试
*/
@Slf4j
@SpringBootTest
public class BaseHandlerChainTests {@Autowired
private MyHandlerChain handlerChain;
@Test
public void handleChain() {
String result = handlerChain.handleChain("zzzzbw");
log.info("handleChain result: {}", result);
}
}
最后日志会输出如下:
INFO 6716 --- [main] c.z.s.demo.chain.handler.MyLogHandler: MyLogHandler hello zzzzbw !
INFO 6716 --- [main] c.z.s.d.chain.handler.MyDefaultHandler: param is zzzzbw
INFO 6716 --- [main] c.z.s.demo.BaseHandlerChainTests: handleChain result: MyDefaultHandler
参考
责任链模式实现的三种方式
责任链模式的两种实现
责任链的三种实现方式比较
责任链的2种实现方式,你更pick哪一种
责任链模式(Chain of Responsibility Pattern)。
这一次搞懂Spring代理创建及AOP链式调用过程 - 云+社区 - 腾讯云 (tencent.com)
【责任链模式在 Spring 中的应用】原文地址: 责任链模式在 Spring 中的应用
推荐阅读
- 责任链模式的两种实现
- java|java c s聊天程序_基于C/S模式的简单聊天程序(附程序源码)
- Python|Python爬虫-获得某一链接下的所有超链接
- 【区块链技术】区块链入门详解①
- Oauth2.0|Oauth2.0基于Spring Authorization Server模块private_key_jwt模式
- 数据获取|poi兴趣点数据获取
- 22种设计模式的C++实现
- VueJs|【Vue 响应式原理】发布订阅模式、观察者模式
- vue|Vue.js响应式原理(三)——发布订阅模式和观察者模式
- 浅谈vue响应式原理及发布订阅模式和观察者模式