Talk about the chain of responsibility mode in Mybatis

foreword

I recently looked at the source code of mybatis plus and found that the Executor under the org.apache.ibatis.executor package path will be packaged into a chain of responsibility when executing SQL. The Interceptor interface is to expand and implement some custom processing before and after executing SQL, such as printing SQL information and putting parameters into SQL for printing; encrypting and decrypting data according to custom annotations, etc. With personal interest, I hope to sort out the responsibility chain model of MyBatis for readers from the perspective of application and source code.

Interceptor (interceptor) interface

To define a custom interceptor, you need to implement the Interceptor interface. Mybatis plus implements a MybatisPlusInterceptor class for us. The specific usage method and the role of @Intercepts and @Signature will not be elaborated in this article. We start to understand the responsibility chain mode of mybatis from the perspective of source code.

The following is the source code of Interceptor:

public interface Interceptor {
    
    

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    
    
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    
    
    // NOP
  }

}

In the interface, there is an intercept method intercept (Invocation invocation) that needs to be implemented by the implementation class, and a plugin (Object target) method implemented by default. This method returns an object, and an empty method setProperties (Properties properties) implemented by default returns a value of void.

The Chain of Responsibility pattern is to enable multiple objects to have the opportunity to process requests and connect these objects into a chain. This sentence is the core of the definition of the Chain of Responsibility pattern.

The plugin method of the default implementation is the core of stringing these objects together, so we look at the specific implementation of Plugin.wrap(target, this) to see what it does. For the clarity of the article, the latter part of the code is omitted.

The core of the wrap method is Proxy.newProxyInstance, which creates a proxy object.

The Plugin class implements JDK's dynamic proxy interface InvocationHandler, so the current class is a proxy class, and the proxy object will enter the invoke method when calling the method.

The target in the Plugin class is the target object, which is the Executor in mybaits; the interceptor is the interceptor we implemented; signatureMap is a signature map, and the specific content is related to the two annotations of @Intercepts @Signature. Those who are interested can learn about it separately.

public class Plugin implements InvocationHandler {
    
    

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    
    
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    
    
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
    
    
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
    
    
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
    
    
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  
  // 省略后续代码..........
InterceptorChain interceptor chain

Our custom interceptor needs to implement the Interceptor interface. At the same time, the custom interceptor will be proxied by the Plugin class. How to form the proxy object into a link is what InterceptorChain needs to do.

public class InterceptorChain {
    
    

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    
    
    for (Interceptor interceptor : interceptors) {
    
    
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    
    
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    
    
    return Collections.unmodifiableList(interceptors);
  }

}

Let's draw a picture based on the code to summarize the link generation
insert image description here

From the figure, we can know that the proxy object is actually generating the proxy object again. In particular, the target attribute of the proxy object cooperates with the proceed method in the Invocation class to form a link call, so that we can do some special custom things before and after we execute SQL.

public class Invocation {
    
    

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    
    
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    
    
    return target;
  }

  public Method getMethod() {
    
    
    return method;
  }

  public Object[] getArgs() {
    
    
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    
    
    return method.invoke(target, args);
  }

}
Summarize

This article uses the source code of mybatis to learn the design pattern of chain of responsibility. For the learning of design patterns, it is not recommended to learn from examples in life. For example, some articles start from the story of the ancient three obediences and four virtues to talk about the chain of responsibility model, which is a bit empty. Personally, I prefer to learn design patterns from the perspective of code.

Guess you like

Origin blog.csdn.net/Tanganling/article/details/130027822