foreword

There is a business scenario in my recent work that is very suitable for using the Chain of Responsibility pattern, and it can be used in several places. In order to write a more general and perfect Chain of Responsibility, read some implementations of Chain of Responsibility in the Spring Framework as a reference.

Application of Chain of Responsibility Pattern in Spring

The application of the chain of responsibility is very extensive, and there are many applications in Spring. Here we analyze how two very commonly used functions are implemented.

HandlerInterceptor in Spring Web

HandlerInterceptor interface is very commonly used in web development. There are three methods: preHandle() , postHandle() , and afterCompletion() . To implement these three methods, you can render "ModelAndView" before calling the "Controller" method, after calling the "Controller" method, and before rendering " ModelAndView" is executed after.

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 plays the role of a handler in the chain of responsibility, and makes a chain of responsibility call through 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);
                }
            }
        }
    }
}

The method of invocation is very simple, that is, HandlerInterceptor registered in Spring is stored in an array, and then interceptorIndex used as a pointer to traverse the chain of responsibility array and call the handlers in sequence.

HandlerExecutionChain

Spring AOP

Spring AOP is more flexible, and can realize various cutting-in timings such as front ( @Before ), rear ( @After ), and surround ( @Around ). There are currently two implementations of Spring AOP's dynamic proxy: JdkDynamicAopProxy and CglibAopProxy . Here, let's take a look at the use of the chain of responsibility with the implementation process of CglibAopProxy .

First, Spring will decide to proxy an object according to the configuration, etc. JdkDynamicAopProxy implements the InvocationHandler interface and the invoke() method. Those who are familiar with JDK dynamic proxy know that when calling a method through the proxy object, it will enter the invoke() method of the InvocationHandler object, so we start directly from this method of 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;
        }
        ...
    }
}

The overall process of this method is relatively long, and I only show the part related to the chain of responsibility. Step 2 By obtaining all Advice and Advice related to the target proxy method, it can be said that the interceptor is encapsulated in Spring, that is, the encapsulation of the AOP method we wrote. Then if there is an interceptor, a ReflectiveMethodInvocation is generated through step 3, and the chain of responsibility is executed.

ReflectiveMethodInvocation 's see how the proceed() method of 06235392f311df looks like:

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. First, ReflectiveMethodInvocation saves the interceptor list ( JdkDynamicAopProxy ) passed in by chain to interceptorsAndDynamicMethodMatchers in the constructor.
  1. Then in the proceed() method, first determine whether the current currentInterceptorIndex pointer reaches the size of the interceptor list. If it does, it means that the interceptors have all been executed, and then call the target proxy method.
  2. Otherwise, get the current interceptor, usually the type is InterceptorAndDynamicMethodMatcher . InterceptorAndDynamicMethodMatcher are only two attributes in 06235392f3132f, MethodMatcher and MethodInterceptor . The former is used to determine whether the interceptor needs to match the target proxy method, and the latter is the interceptor itself.
  3. If the current InterceptorAndDynamicMethodMatcher matches the target proxy method, the current interceptor is called, otherwise the current proceed() is called directly again to form recursion.

ReflectiveMethodInvocation is the "chain" in the chain of responsibility, and the handler is InterceptorAndDynamicMethodMatcher , more precisely the InterceptorAndDynamicMethodMatcher implementation class in MethodInterceptor . The 06235392f313a0 here is encapsulated as various MethodInterceptor in Advice , for example, the surrounding ( @Around ) aspect will be encapsulated as AspectJAroundAdvice .

Don't read the detailed code of AspectJAroundAdvice , first write a basic AOP aspect

@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;
    }
}

The matching rule in @Pointcut will be encapsulated into InterceptorAndDynamicMethodMatcher of MethodMatcher as the matching rule, and the method annotated with @Around will be encapsulated into MethodInterceptor , which is AspectJAroundAdvice here. In AspectJAroundAdvice , parameters such as 06235392f31400 will be encapsulated, and then the corresponding AOP method will be called as an ProceedingJoinPoint parameter reflection.

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();
        }
    }
}

The input parameter ProceedingJoinPoint contains some information about the target proxy method. More importantly, we will call the proceed() method of the object to try to call the target proxy method. That is, we can write the code we expect to be executed before and after the target proxy method before and after the AOP aspect calls proceed() method.
For example, in LogAop , 06235392f3144a will be printed before the target proxy method is executed, and LogAop start will be printed after LogAop end .

How does ProceedingJoinPoint.proceed() do it, here is the code of Spring's implementation class MethodInvocationProceedingJoinPoint .

public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint, JoinPoint.StaticPart {
    @Override
    public Object proceed() throws Throwable {
        return this.methodInvocation.invocableClone().proceed();
    }
}

A very simple piece of code, clone methodInvocation and call its proceed() method. The 06235392f3148c here is the methodInvocation mentioned at the ReflectiveMethodInvocation , so after a large circle, the recursion of ReflectiveMethodInvocation.proceed() is actually formed.

ReflectiveMethodInvocation
![]()

Two implementation summary

HandlerInterceptor in Spring Web uses the sequential traversal mode of the array to control the promotion of the chain of responsibility chain. This mode allows the processor to not need to manually control the chain, and each processor will not interfere with each other. But at the same time, the handler cannot break the chain of responsibility, and it will not end until all handlers are processed. And the call timing of the handler is also determined by the chain, which is relatively inflexible.

Spring AOP adopts a recursive method, and the processor needs to push the chain with the calls displayed in its own function, which is relatively flexible, and can decide the timing of promotion by itself, and even interrupt the chain of responsibility. In this case, it is a bit uncontrollable for other handlers, and sometimes the chain of responsibility is interrupted by other handlers, resulting in themselves not being called, which increases the difficulty of debugging.

These two chains of responsibility chain use the Index variable to control the chain push, which means that a chain cannot be "shared", and the chain of responsibility must be regenerated every time it is used, and it needs to be destroyed when it is used up, which consumes more processor resources.

Designing a Universal Chain of Responsibility

I learned the implementation of other responsibility chains in the front, and now implement a general responsibility chain myself.

First write a basic processor interface:

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

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

    default boolean isHandler(Param param) {
        return true;
    }
}

The processor interface has two methods, isHandler() is used to determine whether the processor needs to be executed, the default is true . doHandle() method executes the processor logic, and returns HandleResult to return the processing result and determine whether to continue executing the next processor.

@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);
    }
}

Finally write the code for the Chain of Responsibility controller:

/**
 * 通用责任链模式
 * <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;
    }
}

This completes the chain of responsibility, now let's do a simple demonstration

/**
 * 基于业务的基础处理器接口
 */
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);
    }
}

The final log will output the following:

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

refer to

Three Ways to Implement the Chain of Responsibility

Two Implementations of the Chain of Responsibility Pattern

Comparison of Three Implementations of Chain of Responsibility

Two implementations of the chain of responsibility, which one do you pick more

Chain of Responsibility Pattern.
the Spring proxy creation and AOP chain invocation process this time- Cloud+Community- Tencent Cloud (tencent.com)


Original address: Application of Chain of Responsibility Pattern in Spring


zzzzbw
922 声望383 粉丝

Playing and Coding