Mybatis framework source code notes (8) Principle analysis of Plugin plug-in

1. Plug-in overview

Quoting a translation from the official website

MyBatis allows you to intercept calls at a certain point during the execution of a mapped statement. By default, MyBatis allows the use of plug-ins to intercept method calls including:

  • 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 classes 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 invocations, you'd better have a good understanding of the behavior of the method you want to override. Because when trying to modify or override the behavior of existing methods, it is likely to break the core module of MyBatis. These are lower-level classes and methods, so be careful when using plug-ins.

2. Custom plug-in implementation example

2.1 Custom plug-in implementation steps

The steps to implement a custom plug-in are roughly as follows:

  • Implement the Interceptor interface of the Mybatis framework

  • Just configure the custom plug-in in the global configuration file mybatis-config.xml

The following is a sample code to demonstrate the specific process.

2.2 Custom plug-in sample code

Let me demonstrate here the interception method of a customized SQl statement. After the execution of the SQL statement is completed, a specific attribute of each output object in the returned collection is modified (this is just to demonstrate how to use it. The demonstration code scenario cannot be universal and cannot be used. Please inform me about production, if you are interested, you can research and develop by yourself)

Custom interceptorMyInterceptor
Insert image description here

package com.kkarma.plugins;

import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Properties;


/**
 * @author kkarma
 * @date 2023/1/17
 */
@Intercepts({
    
    
        @Signature(type = Executor.class, method = "query",
                args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyInterceptor implements Interceptor {
    
    

    private String author;

    /**
     * 执行拦截逻辑的方法
     * @param invocation
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        System.out.println("method will be invoked......");
        List<LibBook> list = (List<LibBook>)invocation.proceed();
        list.stream().forEach(v -> v.setBookName(v.getBookName() + "----mod"));
        System.out.println("method has been invoked......");
        return list;
    }

    /**
     * 是否触发intercept方法
     * @param target
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    /**
     * 自定义插件属性参数配置
     * @param properties
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public void setProperties(Properties properties) {
    
    
        Object author = properties.get("author");
        System.out.println(author);
    }

    public String getAuthor() {
    
    
        return author;
    }

    public void setAuthor(String author) {
    
    
        this.author = author;
    }
}

Modify the global configuration file mybatis-config.xml

这里注意Mybatis-config.xml文件中各个标签元素的声明顺序
Insert image description here

Introduce custom plug-ins in the global configuration file
Insert image description here

    <plugins>
        <plugin interceptor="com.kkarma.plugins.MyInterceptor">
            <property name="author" value="kkarma"/>
        </plugin>
    </plugins>

The above are all the implementation steps of the custom plug-in. Is it very simple? Let’s test it:

2.3 Custom plug-in sample code testing

Let's write a unit test to query all the data in a single table in the database to see if our custom plug-in takes effect.

@Test
public void testInterceptor() {
    
    
	SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
    try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
    
    
        SqlSessionFactory factory = factoryBuilder.build(ins);
        SqlSession sqlSession = factory.openSession(true);
        SqlSession sqlSession1 = factory.openSession(true);
        LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
        List<LibBook> libBooks = mapper.selectAllBook();
        libBooks.forEach(System.out::println);
        System.out.println("------------------------------------------");
        sqlSession.close();
    } catch (IOException e) {
    
    
        e.printStackTrace();
    }
}

Insert image description here
Insert image description here
This shows that our custom plug-in has taken effect. From here, we can see that the steps to customize the plug-in are relatively simple. Next, we will analyze the implementation principle of the plug-in through the source code of Mybatis.

3 Analysis of plug-in implementation principles

3.1 How plug-ins are parsed and introduced

In the above example code, we declared our custom Interceptor in Mybatis-config.xml, then there must be a dedicated party in the parsing class of the global configuration file responsible for parsing and processing our custom plug-in module method,
Insert image description here
is called in method of XMLConfigBuilder classparseConfiguration()pluginEelment
Insert image description here

pluginEelmentThe method is used to parse the plugins tag in the global configuration file, then create a corresponding Interceptor object and encapsulate the corresponding attribute information. Finally, the Configuration objectaddInterceptor(interceptorInstance) was called to complete the interceptor registrationInsert image description here
configuration.addInterceptor(interceptorInstance) as follows:
Insert image description here
Insert image description here
Mybatis ofInterceptorChain
Insert image description here

3.2 Who is the plug-in intended for?

We have explained it at the beginning:

MyBatis allows you to intercept calls at a certain point during the execution of a mapped statement. By default, MyBatis allows the use of plug-ins to intercept method calls including:

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

Before we were talking about Executor, StatementHandler , ParameterHandler, ResultSetHandler object creation During the process, we briefly mentioned that interceptors intercept these objects to implement functional expansion. Let's take a look. The above four core classes will call the following code in the
Configuration class if the creation fails:

  • Executor object creation
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : 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);
    return executor;
  }

Our interceptor starts working when we call the Executor.query() method
Insert image description here
Insert image description here

  • StatementHandler object creation
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 这里我们的拦截器链会进行拦截
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    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);
    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);
    return statementHandler;
  }
  • ParameterHandler object creation
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 这里我们的拦截器链会进行拦截
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    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);
    return resultSetHandler;
  }
  • ResultSetHandler object creation
  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);
    return resultSetHandler;
  }

3.1 Processing process of plug-in interception

3.1.1 Interception processing process of a single plug-in

The whole process is combined using the chain of responsibility model

Multiple interceptors are stored in the interceptor chain, and all interceptors will be traversed and the Interceptor.plugin(Object target) method will be called.

The Interceptor.plugin(Object target) method will call the wrap() method of the Plugin class to return the dynamic proxy object of the interception target object.

The Plugin class implements the InvocationHandler interface, so the invoke() method of the proxy object will be called in the end.

The invoke() method of the Plugin class calls the intercept (Invocation invocation) method of the custom interceptor. Here, our interception processing logic will be called before and after the original method that needs to be executed to extend the input and output of the method.
Insert image description here
Insert image description here
Insert image description here

3.1.2 Interception processing process of multiple plug-ins

If we have multiple custom interceptors, what is its interception execution process?

Here I have added an interceptor plug-in, which intercepts the method. Let us verify our conjecture through specific code examples. Executor.classquery

package com.kkarma.plugins;

import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Properties;


/**
 * @author kkarma
 * @date 2023/1/17
 */
@Intercepts({
    
    
        @Signature(type = Executor.class, method = "query",
                args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyLogInterceptor implements Interceptor {
    
    

    private String logger;

    /**
     * 执行拦截逻辑的方法
     * @param invocation
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        System.out.println("拦截方法执行之前记录日志...");
        List<LibBook> list = (List<LibBook>)invocation.proceed();
        System.out.println("拦截方法执行之后再次记录日志...");
        return list;
    }

    /**
     * 是否触发intercept方法
     * @param target
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    /**
     * 自定义插件属性参数配置
     * @param properties
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public void setProperties(Properties properties) {
    
    
        Object logger = properties.get("logger");
        System.out.println(logger);
    }

    public String getLogger() {
    
    
        return logger;
    }

    public void setLogger(String logger) {
    
    
        this.logger = logger;
    }
}

The order of registering plug-ins in mybatis-config.xml is as follows:

    <plugins>
        <plugin interceptor="com.kkarma.plugins.MyInterceptor">
            <property name="author" value="kkarma"/>
        </plugin>
        <plugin interceptor="com.kkarma.plugins.MyLogInterceptor">
            <property name="logger" value="myLogger"/>
        </plugin>
    </plugins>

After executing the call, the console prints the result as shown below:
Insert image description here

3.1.2.1 Interceptor registration process

First, the order we register in the configuration file is MyInterceptor -> MyLogInterceptor
Insert image description here

3.1.2.2 Creation process of proxy object

The Interceptors objects created by parsing from the configuration file must be parsed in the order of definition, so when dynamic agents are created here, they are also created in the order of registration. MyInterceptor -> MyLogInterceptor
Insert image description here

3.1.2.3 Calling process of dynamic proxy object

Insert image description here
Continue execution and find that the intercept() method of MyInteceptor is executed, and then the query() method is executed, returning from the inside to the outside in sequence.
Insert image description here
From the print log, we can see that the order of 执行调用的顺序 and 注册和创建动态代理对象 is exactly the opposite.
Insert image description here

3.1.2.4 Conclusion
  • Register multiple plug-ins toInterceptorChain The List is parsed and added from top to bottom in the order defined by the plug-ins in the configuration file.
  • When creating a proxy object, it is also proxied in the order of the List of InterceptorChain
  • The process of calling and executing is exactly the same as the process of registration and creation相反.

The registration creation process (the process of wrapping gifts):

It’s just that we now have a bunch of beautiful space sand (target object) that we want to give to other children. We can’t give it away directly.

So you found a bottle (MyInteceptor) and filled it with space sand.

But the bottle is also ugly, so you package it in an exquisite gift box (MyLogInteceptor). Now it looks better, and you give it to your friend.

Call the execution process (the process of opening gifts):

When your friend receives a gift, she will open the gift box first (MyLogInteceptor.intercept()) is called, and she will find that the space sand is being rotated in a bottle.

So he opened the bottle (MyInteceptor.intercept()) again and poured out the space sand (target object). Now she can play happily.

Insert image description here
The interceptors of StatementHandler, ParameterHandler, and ResultSetHandler have the same processing flow as the Executor. I won’t go into details here. If you are interested, you can study it yourself.

Guess you like

Origin blog.csdn.net/qq_41865652/article/details/129497582
Recommended