This article is the summary note of the book “MyBatis: Technical Principles and Practice”.

MyBatis analysis and operation principle, including the construction of SqlSessionFactory and SqlSession execution process, which, SqlSession contains four objects, you can insert custom code when the four objects scheduling, to meet special needs. This is the plug-in technology provided by MyBatis.

Series index:

  1. Introduction to JDBC and MyBatis
  2. All configurations of MyBatis
  3. Mapper knows all about it
  4. Reflection and dynamic proxy fundamentals
  5. MyBatis parsing and operating principle

Plug-ins need to be used to handle some special scenarios. For example, in multi-tenant development, data needs to be isolated by tenant. You can add tenant id filtering criteria at the end of SQL statements.

This article introduces the plugin. Through the introduction of this article, you will know:

  • Plug-in interface and initialization
  • Proxy and reflection design for plug-ins
  • Introduction to the MetaObject tool class
  • Plug-in development process

Interface and initialization analysis of plug-ins

Plug-in interface

MyBatis provides the Interceptor interface, which is defined as follows:

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  
  Object plugin(Object target);
  
  void setProperties(Properties properties);
}
Copy the code

Elaborate on these three methods:

  • Intercept: This will override the object you intercept directly. There is a parameter Invocation object from which the method used to dispatch the original object is reflected;
  • Plugin: target is the object being intercepted. It generates a proxy object for the object being intercepted.
  • SetProperties: Allows you to configure the required parameters in the plugin element. This method is called once during plugin initialization.
Plug-in initialization

The initialization of the plug-in is completed when MyBatis is initialized, read in the plug-in node and configured parameters, use reflection technology to generate the plug-in instance, then call the setProperties method in the plug-in method to set parameters, and save the plug-in instance to the configuration object, see the following code for the specific process.

The configuration example of plugin is as follows:

<plugins>
    <plugin interceptor="com.qqdong.study.mybatis.TenantPlugin">
        <property name="dbType" value="mysql"/>
    </plugin>
<plugins>
Copy the code

Plug-in initialization process:

public class XMLConfigBuilder extends BaseBuilder {...private void pluginElement(XNode parent) throws Exception {
    if(parent ! =null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); }}}... }Copy the code

Add a plug-in to the Configuration object:

public class Configuration {
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }}Copy the code

InterceptorChain is a class containing a List property that holds the Interceptor object:

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  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(a) {
    returnCollections.unmodifiableList(interceptors); }}Copy the code

Proxy and reflection design principles for plug-ins

Chain of Responsibility model

Plug-ins use the chain of responsibility pattern, which is an object behavior pattern. In the chain of responsibility pattern, many objects are connected in a chain by each object’s reference to its next parent, and requests are passed along the chain until one of the objects on the chain decides to process the request.

Design details

The InterceptorChain class mentioned earlier has a pluginAll method where the responsibility chain is defined.

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

The Plugin method, described above, is a method that generates a proxy object, passing it to the Plugin method from the first object (one of the four), and returning a proxy. If a second plug-in exists, it takes the first proxy object, passes it to the plugin method, and returns the proxy for the first proxy object…..

MyBatis provides the plugin tool class, which implements the InvocationHandler interface (JDK dynamic proxy interface), look at its two methods:

public class Plugin implements InvocationHandler {
  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) {
      throwExceptionUtil.unwrapThrowable(e); }}}Copy the code

Plugin provides the static wrap method, which generates a proxy class using JDK dynamic proxy methods according to the signature configuration of the plug-in. When the four objects execute a method, Plugin’s invoke method will be called. If the method is included in the declaration signature, The intercept method of the custom plug-in is called, passing in the Invocation object.

In addition, the Invocation object contains a proceed method, this method is invoked by proxy objects real method, if there are n plug-in, first represents the parameters of four big object itself, and then call a wrap method to produce the first proxy objects, here is the reflection of real method of four large objects, if there is a second plugin. The reflection here is the invoke method of the first proxy object.

So, in the case of multiple plug-ins, scheduling the proceed method, MyBatis always runs from the last proxy object to the first, and finally the actual intercepted object method is executed.

Introduction to the MetaObject tool class

MetaObject is a tool class provided by MyBatis, which can effectively obtain or modify the attributes of some important objects.

For example, we intercept the StatementHandler object by first retrieving the SQL it is to execute and adding a limit on the number of rows returned.

Write a custom plug-in that implements the Intercept method as follows

StatementHandler statementHandler=(StatementHandler)invocation.getTarget();
MetaObject metaObj=SystemMetaObject.forObject(statementHandler);

/ / access to SQL
String sql=(String)metaStatementHandler.getValue("delegate.bound.sql");
// Add the limit condition
sql="select * from (" + sql + ") limit 1000";
// Reset the SQL
metaStatementHandler.setValue("delegate.bound.sql",sql);
Copy the code

Plug-in development process

Finally, summarize the development steps of the plug-in.

Determine the signature to intercept
  • Determine the object to intercept, one of the four;
  • Determine interception methods and parameters;

For example, if you want to intercept the prepare method of the StatementHandler object, which takes a Connection object, you can declare it as follows:

@Intercepts({
    @Signature(type =StatementHandler.class,
        method="prepare" , 
        args={Connection.class})})
public class MyPlugin implements Interceptor{... }Copy the code
Define plug-in classes to implement intercepting methods

The principle has been analyzed above, implementing the Interceptor interface method can be used, Plugin tool class is convenient to generate proxy class, MetaObject tool class is convenient to operate the properties of the four objects, modify the corresponding value.

configuration

Finally, configure the custom plug-in:

<plugins> <plugin interceptor="com.qqdong.study.mybatis.TenantPlugin"> <property name="dbType" value="mysql"/> </plugin>  <plugins>Copy the code

Custom plug-in is more complex, if you do not understand the principle, it is easy to make mistakes, can not use the plug-in as far as possible, because it is to modify the underlying design of MyBatis. Plug-in is generated layer of proxy object responsibility chain mode, through the reflection method to run, the performance is not high, to consider the overall, especially multiple plug-in layer of proxy logic.

Please scan the qr code below and follow my personal wechat official account ~