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. Sorted out a 272 page Mybatis study notes

<? The 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> 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() { return Collections.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); // Plugin.wrap(target, this); // The intercept() method returns 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) {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, Invocation); args)); } return method.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(); / / here shows the setProperties () method call time interceptorInstance. 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. Sorted out a 272 page Mybatis study notes

public interface StatementHandler { <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; BoundSql getBoundSql(); } public class BoundSql { public String getSql() { return sql; }}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.