1. Custom plug-in reminder

MyBatis allows us to intercept calls at some point during the execution of mapped SQL statements. By default, MyBatis allows you to intercept method calls using plug-ins:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

The details of the methods in these interfaces can be discovered by looking at the signature of each method, or by looking directly at the source code in the MyBatis distribution. If you want to do more than monitor method calls, you’d better know a lot about the behavior of the method you’re overwriting. If you try to modify or override the behavior of an existing method, you may be breaking the core module of MyBatis. These are lower-level classes and methods, so be careful when using plug-ins.


2. Customize plug-ins

MyBatis provides a powerful mechanism, using the plug-in is very simple, just implement the Interceptor interface, and specify the method signature you want to intercept.

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

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

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

  public void setProperties(Properties properties) {}}Copy the code


3. Plug-in configuration mode

After the plug-in is defined, it can be used in MyBatis-config.xml:

<plugins>  
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin" >    
        <property name="pluginProperty" value="100"/>  
    </plugin>
</plugins>Copy the code

If you are using a Spring project, you can also use it by injecting SqlSessionFactoryBean:

ExamplePlugin ExamplePlugin = new ExamplePlugin(); Properties properties = new Properties(); properties.setProperty("xxx"."xxx");

examplePlugin .setProperties(properties);

//添加插件
factory.setPlugins(new Interceptor[]{examplePlugin });Copy the code


4. How is the plug-in object created

In ExamplePlugin, there is a plugin(Object target) method that internally calls the plugin. warp(Object target, Interceptor Interceptor) method, The warp method creates a proxy object for the Target object. This created proxy object is the plug-in object we want. So when and where was this object created?

In Mybatis, create proxy object proxy class only one, is the above said Plugin class, source code is as follows:

Public class Plugin implements InvocationHandler {private final Object target; 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; } // If the interceptor supports target intercepting, create a proxy object for target. If the interceptor supports target intercepting, create a proxy object for target. Public static Object wrap(Object target, Interceptor Interceptor) {// The @interceptors annotation defines the content of the Map<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor); // Get target's bytecode object Class<? >type= target.getClass(); / / according totypeAnd signatureMap, gettypeFind all matching interceptors support intercepting interface bytecode object array Class<? >[] interfaces = getAllInterfaces(type, signatureMap); // If the size of interfaces is greater than 0, a proxy object is created for the target object. Otherwise, no proxy object is createdif(interfaces.length > 0) {// Create a proxy object for target. The JDK dynamic proxy is usedreturn Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));    
        }    
        returntarget; } // Implement the INVOKE method of the JDK's InvocationHandler interface. When a proxy object is called, the invoke method is executed inside the proxy method. Invoke will have the target method call judgment and // notification execution logic. Method. Invoke (target, args); // Invoke (target, args); The call to the target invoke method executes until the original proxied // object, @override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {try {// Obtains the bytecode Object of the class or interface to which method belongs Set< method > methods = signatureMap.get(method.getDeclaringClass()); // Check whether methods include method, if so, execute the intercept methodif(methods ! = null && methods.contains(method)) {returninterceptor.intercept(new Invocation(target, method, args)); } // Invoke method to proceed to the next level of invocationreturnmethod.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map<Class<? >, Set<Method>> getSignatureMap(Interceptor Interceptor) {// Get the bytecode object corresponding to the @intercepts annotation interceptsAnnotation Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // If the interceptsAnnotation is empty, throw an exception with a plug-in definition errorif (interceptsAnnotation == null) {     
            throw new PluginException("No @Intercepts annotation was found in interceptor "+ interceptor.getClass().getName()); } / / get all @ Intercepts all the Signature in the Signature [] sigs. = interceptsAnnotation value (); / / definetypeMap<Class<? >, Set<Method>> signatureMap = new HashMap<>(); // Initialize signatureMap by traversing sigSfor (Signature sig : sigs) {      
            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("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: "+ e, e); // Mapping of storage interface bytecode objects and interface method bytecode objectsreturnsignatureMap; } // Check whether the target object (one of the Executor, StatementHandler, ParameterHandler, ResultSetHandler interfaces) is blocked by an interceptor.type: the bytecode object of the target object; SignatureMap: is the private static Class<? >[] getAllInterfaces(Class<? >type, Map<Class<? >, Set<Method>> signatureMap) { Set<Class<? >> interfaces = new HashSet<>();while (type! = null) {// gettypeThe parent interface, and traversalfor(Class<? > c: type.getinterfaces ()) {// Check whether signatureMap contains interfacestypeThe parent interfaceif(signatureMap.containsKey(c)) { interfaces.add(c); }} // gettypeParent class, continuewhilecycletype= type.getSuperclass(); } // Return all interface bytecode objects matchedreturn interfaces.toArray(new Class<?>[interfaces.size()]);  
   }
}
Copy the code

The Plugin class implements the InvocationHandler interface in the JDK. Contains five methods and three fields. Above the source code analysis of the basic each sentence have made annotations, understand should have no problem, after all, we are old drivers.

Five of these methods work:

(1) getSignatureMap(Interceptor Interceptor){……. } method:

Parse the value in the @intercepts annotation of the Interceptor. Returns a map of the collection of interface bytecode objects and intercepting methods.

For example, in the ExamplePlugin above, the @intercepts annotation reads:

@Intercepts({
  @Signature(
  type= Executor.class,
  method = "query",  args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})Copy the code

The parsed Map contains the following values:




(2) wrap(Object target, Interceptor Interceptor) {….. } method:

If the interceptor supports target intercepting, create a proxy object for target. If the interceptor does not support target intercepting, return the original target.


(3) getAllInterfaces (Class <? > type, Map<Class<? >, Set<Method>> signatureMap){…… } method:

According to the given type bytecode object, get the key value in signatureMap and return it as a collection.




(4) Invoke (Object Proxy, Method Method, Object[] args){…… } method:

The invoke method is the entry to the interceptor executed by MyBatis. The specific execution logic has been annotated in the above source code analysis, and the old iron can be flipped up. But there’s a line of code in this method that I need to explain, and it looks like this:

Set< method > methods = signaturemap. get(method.getDeclaringClass());Copy the code

In order for method to fetch methods from signatureMap, method must be one of the four interfaces used by the interceptor, i.e

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

Method in. The key stored in signatureMap is the bytecode object of the above four interfaces. You can see below:

Examples of keys and values stored in signatureMap:



Signaturemap.get (method.getDeclaringClass())) returns a bytecode object from one of MyBatis’ four interfaces.




5. To summarize

About the creation of MyBatis plug-in analysis to this, for the specific use of plug-ins or that a few words. The plugin will affect the execution behavior of MyBatis, please be cautious. The next article will look at where interceptors are used in MyBatis, i.e. where the plug-in is invoked in MyBatis.


For the plug-in design of MyBatis, see juejin.cn/post/684490…


There is no place for analysis, or there is doubt, welcome to leave a message to share, we have worked together.


In addition, welcome to pay attention to my public number, together with the promotion.


“The rest of your life is long. Don’t panic.”