Mybatis source code learning - plugin package

Mybatis series article navigation

MyBatis provides plug-in functions, allowing other developers to develop plug-ins for MyBatis to extend the functions of MyBatis. With this plug-in function, developers can extend the functions of MyBatis.

Develop a plug-in

Before learning the source code, we must first know what functions it has and what we can do with this function. So I decided to take you to develop a plug-in to print query results on the console.

Create plug-in class

@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class MyInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object results = invocation.proceed();
    System.out.println("Query results:");
    for (Object result : ((List) results)) {
      System.out.println(result);
    }
    return results;
  }
}

It can be found that I also declare some annotations on the class. Intercepts indicate that the current class is a plug-in class. Because the interceptor is general, we need to declare the methods we need to intercept through Signature. If there are multiple methods, we need to set multiple signatures.

  • type: the name of the class to intercept.
  • Method: name of the method to intercept.
  • args: type of method parameter to intercept.

Since we intend to intercept the following methods, the configuration is as shown in the above code. And because the return value is List, we can safely convert the result to List type.

public interface ResultSetHandler {
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
}

Configure plug-ins

In addition to creating the corresponding plug-in class, you also need to create the Configure this plug-in in XML.

  <plugins>
    <plugin interceptor="com.yinxy.plugin.MyInterceptor"/>
  </plugins>

Effect display

Console print information

Query results:
User(id=1, name=Jone, age=18)
User(id=2, name=Jack, age=20)
User(id=3, name=Tom, age=28)
User(id=4, name=Sandy, age=21)
User(id=5, name=Billie, age=24)
....

plugin package

Class diagram
[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-lmot9dyk-163048346241) (media / 16300505124116 / 16300531480986. JPEG)]

Plugin

In the plugin package, the most critical is the plugin class, which inherits Java lang.reflect. Invocationhandler is the base dynamic proxy class.

Member variable

  // Proxied object
  private final Object target;
  // Interceptor
  private final Interceptor interceptor;
  // The method to be intercepted is extracted from the Signature annotation
  private final Map<Class<?>, Set<Method>> signatureMap;

Gets the method to intercept

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // Get Intercepts annotation
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) {
      throw new PluginException("");
    }
    
    // Get Signature annotation
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    // Traverse all Signature annotations to get the intercepted method
    for (Signature sig : sigs) {
      // Put the same class method into the same Set collection
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("");
      }
    }
    return signatureMap;
  }

Generate proxy class
Loading a plug-in is actually generating a proxy class.

  public static Object wrap(Object target, Interceptor interceptor) {
    // Get all the methods that need to be intercepted
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // Get all the interfaces to be implemented by the proxy object
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // Because JDK dynamic proxy is used, an interface is required to generate the corresponding proxy object
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // Return the original object directly
    return target;
  }

Need interception
Because the plug-in is universal, the Executor may load a plug-in that intercepts ResultSetHandler, so you need to judge whether the plug-in can intercept the proxied object.

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // Determine whether to intercept the current method of the proxy object
      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);
    }
  }

Invocation

Invocation is responsible for transferring data between interceptors.

public class Invocation {
  // Proxied object
  private final Object target;
  // Method called
  private final Method method;
  // Request parameters
  private final Object[] args;
  
  // Execution method
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}

InterceptorChain

InterceptorChain stores all interceptors and can load interceptors for objects.

public class InterceptorChain {

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

  public Object pluginAll(Object target) {
    // Plug in loading
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
 
  // Add plug-in
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

}

In fact, the overall code of the plugin package is not complex. The important thing is that it uses the responsibility chain design pattern. If you don't understand the responsibility chain design pattern, it may be difficult to understand. So far, I have explained the whole MyBatis source code, and the annotated source code has been uploaded to GitHub.

reference

  1. General source code reading guide: Mybatis source code reading details - Yige

Keywords: Java Mybatis source code

Added by name1090 on Fri, 17 Dec 2021 16:18:27 +0200