Most frameworks support plug-ins, users can write plug-ins to extend their own functions, Mybatis is no exception.

We expounding from the following six aspects: plug-in configuration, plug-in writing, plug-in running principle, plug-in registration and interception timing, plug-in initialization, and principle of paging plug-in.

1. Configure plug-ins

Mybatis plug-ins are configured inside the Configuration object. During initialization, these plug-ins will be read and stored in the InterceptorChain of the Configuration object.

<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE configuration PUBLIC"- / / mybatis.org//DTD Config / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
    <plugin interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
      <property name="value" value="100" />
    </plugin>
  </plugins>
</configuration>
Copy the code
public class Configuration {
    protected final InterceptorChain interceptorChain = new InterceptorChain();
}
Copy the code

Org. Apache. Ibatis. Plugin. InterceptorChain. Java source code.

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

The for loop above represents a chain of responsibility for any plug-in (don’t expect it to skip a node), which is essentially an interceptor.

2. How to write a plug-in

The plugin must implement org. Apache. Ibatis. Plugin. The Interceptor interface.

public interface Interceptor {
  
  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}
Copy the code

Intercept () method: a place where an intercept is executed, such as in order to collect protection money. Triggered by the plugin() method, interceptor.plugin(target) is proof enough.

Plugin () method: Determines whether to trigger the Intercept () method.

The setProperties() method: passes xmL-configured property parameters to a custom interceptor.

Here’s a custom interceptor:

@Intercepts({
    @Signature(type = Executor.class, method = "query",
    args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }),
    @Signature(type = Executor.class, method = "close",     
    args = { boolean.class }) })
public class MyBatisInterceptor implements Interceptor {

  private Integer value;

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    System.out.println(value);
        The Plugin class is the core plug-in class that creates a JDK dynamic proxy object for Target that triggers the Intercept () method
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
    value = Integer.valueOf((String) properties.get("value")); }}Copy the code

Faced with the above code, we need to solve two questions:

1. Why write an Annotation? What do the annotations mean?

A: Mybatis specifies that plugins must write Annotation annotations. It is mandatory, not optional.

The @intercepts annotation loads an @signature list. An @signature is a method wrapper that needs to be intercepted. An interceptor that intercepts multiple methods is a @signature list.

type = Executor.class, 
method = "query", 
args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }
Copy the code

Explanation: To intercept the Query () method in the Executor interface, the argument type is args list.

2. Plugin.wrap(target, this)

A: Using the JDK’s dynamic proxy, method interception and enhancement is implemented by creating a Delegate proxy object for the Target object, which calls back to the Intercept () method.

Org. Apache. Ibatis. Plugin. Plugin. Java source code:

public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; private 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) {
      // Create JDK dynamic proxy objects
      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());
      // Determine whether the method needs to be intercepted (important)
      if(methods ! = null && methods.contains(method)) {// Call back to the intercept() method
        return interceptor.intercept(new Invocation(target, method, args));
      }
      returnmethod.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); }}/ /...
}
Copy the code

Map<Class<? >, Set> signatureMap: Caches the reflection result of the object to be intercepted to avoid multiple reflections, i.e. the reflection result of the target.

So, let’s not say that reflection performance is poor, it is because you do not cache the reflection results of an object like Mybatis.

Mybatis (Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis)

3. Which interface objects can Mybatis intercept?

public class Configuration {
/ /...
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); / / 1
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); / / 2
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); / / 3
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor); / / 4
    return executor;
  }
/ /...
}
Copy the code

Mybatis can intercept only methods in ParameterHandler, ResultSetHandler, StatementHandler, and Executor interface objects.

Reviewing interceptorChain. PluginAll () method: this method is called when creating the above four interface object, its meaning for the interceptor interface object registration function, attention is registered, rather than the interception.

Interceptor execution timing: After the plugin() method registers the interceptor, execution of the interceptor, i.e. plug-in execution, is automatically triggered when specific methods within the four interface objects are executed.

So, it’s important to know when to register and when to execute. PluginAll () or plugin() should never be considered execution, it is just registration.

4. Invocation

public class Invocation {
  private Object target;
  private Method method;
  private Object[] args;
}
Copy the code

The Intercept (Invocation) method parameter Invocation, I am sure you can understand, do not explain.

5. Initialize the plug-in source code parsing

Org. Apache. Ibatis. Builder. XML. XMLConfigBuilder. ParseConfiguration method (XNode) part of the source code.

pluginElement(root.evalNode("plugins"));

 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();
      // This shows when the setProperties() method is calledinterceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); }}}Copy the code

For Mybatis, it does not distinguish what kind of Interceptor interface it is. All plug-ins are interceptors. Mybatis completely relies on annotations to identify who it intercepts, so it has interface consistency.

6. Principle of paging plug-ins

As Mybatis uses logical paging instead of physical paging, then, there is a Mybatis paging plug-in that can realize physical paging in the market.

To implement physical paging, String SQL intercepts and enhancements are required. Mybatis stores String SQL through BoundSql objects, which are retrieved by StatementHandler objects.

public interface StatementHandler {
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
    BoundSql getBoundSql();
}
Copy the code
public class BoundSql {
   public String getSql() {
    returnsql; }}Copy the code

Therefore, you need to write a Query method interceptor for StatementHandler, and then get the SQL and rewrite it to enhance it.

From: my.oschina.net/zudajun/blog/738973