责任链模式在 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作为指针去遍历责任链数组按顺序调用处理者。

HandlerExecutionChain

Spring AOP

Spring AOP实现的更加灵活,可以实现前置(@Before)、后置(@After)、环绕(@Around)等多种切入时机。目前Spring AOP 的动态代理有两种实现:JdkDynamicAopProxyCglibAopProxy。这里就以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<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			
			if (chain.isEmpty()) {
				// 没有拦截器则直接反射调用目标方法
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// 3 生成动态代理的责任链
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 4 执行责任链
				retVal = invocation.proceed();
			}

			// 5. 组装处理返回值
			...
			return retVal;
		}
		...
	}
}
复制代码

这个方法整体流程比较长的,我只展示了和责任链有关的部分。步骤2通过获取和目标代理方法相关的所有AdviceAdvice可以说是拦截器在Spring中的封装,即我们编写的AOP方法的封装。接着如果有拦截器则通过步骤3生成一个ReflectiveMethodInvocation,并且执行该责任链。

再看看ReflectiveMethodInvocationproceed()方法是怎么样的:

public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

	protected ReflectiveMethodInvocation(
			Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
			@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		this.method = BridgeMethodResolver.findBridgedMethod(method);
		this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}

	public Object proceed() throws Throwable {
		
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			// 1. 如果当前Index和interceptorsAndDynamicMethodMatchers的拦截器数量相同,说明责任链调用结束,直接反射调用目标代理的方法
			return invokeJoinpoint();
		}

		// 2. 获取当前的拦截器
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// InterceptorAndDynamicMethodMatcher 类型的拦截器,校验MethodMatcher是否匹配

			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			// 3. 目标代理方法和当前拦截器是否matches
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				// 4. 执行当前拦截器
				return dm.interceptor.invoke(this);
			}
			else {
				// 5. 不匹配则递归调用下一个拦截器
				return proceed();
			}
		}
		else {
			// 执行当前拦截器
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}
}
复制代码
  1. 首先ReflectiveMethodInvocation在构造方法中保存JdkDynamicAopProxy传入的拦截器列表(chain)到interceptorsAndDynamicMethodMatchers
  2. 接着在proceed()方法中,先判定当前currentInterceptorIndex指针是否到拦截器列表size,如果到了说明拦截器都执行过了,就去调用目标代理方法。
  3. 否则获取当前拦截器,通常类型为InterceptorAndDynamicMethodMatcherInterceptorAndDynamicMethodMatcher里面只有两个属性,MethodMatcherMethodInterceptor,前者用于判定拦截器是否需要匹配目标代理方法,后者就是拦截器本身。
  4. 如果当前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里的匹配规则会被封装到InterceptorAndDynamicMethodMatcherMethodMatcher作为匹配规则,而@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()的递归。

ReflectiveMethodInvocation

两种实现总结

Spring Web 中的 HandlerInterceptor采用数组的顺序遍历模式来控制责任链链条的推动,这种模式可以让处理者无需手动控制链条,每个处理者之间也不会相互收到干扰。但是同时处理者就无法打断责任链,必须处理完所有的处理者才会结束。且处理者的调用时机也是由链来决定,相对没那么灵活。

Spring AOP则采用递归的方式,处理者要在自己的功能里显示的调用来推动链条,这样相对比较灵活,可以自己决定推进时机,甚至可以打断责任链。这样的话对其他处理者来说有点不可控,有时责任链被其他处理者打断而导致自己未被调用到,增加了调试的困难。

这两种责任链的链条都使用Index变量来控制链条推动,这就意味着无法“共享”一个链条,每次使用责任链都要重新生成,用完又需要销毁,比较消耗处理器资源。

设计通用责任链

在前面学习了其他责任链的实现,现在自己实现一个通用的的责任链。

先写一个基础的处理器接口:

/**
 * 通用责任链的 Handler
 * <p>
 * 每个业务声明一个该接口的子接口或者抽象类,再基于该接口实现对应的业务 Handler。
 * 这样 BaseHandlerChain 可以直接注入到对应的 Handler List
 */
public interface BaseHandler<Param, Result> {

    @NonNull
    HandleResult<Result> doHandle(Param param);

    default boolean isHandler(Param param) {
        return true;
    }
}
复制代码

处理器接口有两个方法,isHandler()用于判定该处理器是否需要执行,默认为truedoHandle()方法执行处理器逻辑,且返回HandleResult用于返回处理结果,并判定是否继续执行下一个处理器。

@Getter
public class HandleResult<R> {
    private final R data;

    private final boolean next;

    private HandleResult(R r, boolean next) {
        this.data = r;
        this.next = next;
    }

    public static <R> HandleResult<R> doNextResult() {
        return new HandleResult<>(null, true);
    }

    public static <R> HandleResult<R> doCurrentResult(R r) {
        return new HandleResult<>(r, false);
    }
}
复制代码

最后编写责任链控制器的代码:

/**
 * 通用责任链模式
 * <p>
 * 使用方法:
 * <p>
 * 1. 创建一个对应业务的责任链控制器, 继承 BaseHandlerChain,
 * 如: {@code MyHandlerChain extends BaseHandlerChain<MyHandler, MyParam, MyResult>}
 * <p>
 * 2. 创建一个对应业务的责任链处理器 Handler,继承 BaseHandler,
 * 如: {@code MyHandler extends BaseHandler<MyParam, MyResult>}
 * <p>
 * 3. 编写业务需要的处理器 Handler 实现 MyHandler 接口的 doHandle 方法。推荐把控制器和处理器都交给 Spring 控制,可以直接注入。
 */
public class BaseHandlerChain<Handler extends BaseHandler<Param, Result>, Param, Result> {

    @Getter
    private final List<Handler> handlerList;


    public BaseHandlerChain(List<Handler> handlerList) {
        this.handlerList = handlerList;
    }

    public Result handleChain(Param param) {
        for (Handler handler : handlerList) {
            if (!handler.isHandler(param)) {
                continue;
            }
            HandleResult<Result> result = handler.doHandle(param);
            if (result.isNext()) {
                continue;
            }
            return result.getData();
        }

        return null;
    }
}
复制代码

这样就责任链就完成了,现在来做个简单的使用演示

/**
 * 基于业务的基础处理器接口
 */
public interface MyHandler extends BaseHandler<String, String> {
}

/**
 * 基于业务的控制器接口
 */
@Service
public class MyHandlerChain extends BaseHandlerChain<MyHandler, String, String> {

    @Autowired
    public MyHandlerChain(List<MyHandler> myHandlers) {
        super(myHandlers);
    }
}

/**
 * 处理器1
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyLogHandler implements MyHandler {

    @Override
    public @NonNull HandleResult<String> doHandle(String param) {
        log.info("MyLogHandler hello {} !", param);
        return HandleResult.doNextResult();
    }
}

/**
 * 处理器2
 */
@Slf4j
@Component
public class MyDefaultHandler implements MyHandler {

    @Override
    public @NonNull HandleResult<String> 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 中的应用

猜你喜欢

转载自juejin.im/post/7076480811070914574
今日推荐