 MyBatis的插件开发运行原理
MyBatis的插件开发运行原理
  # MyBatis 的插件运行原理
MyBatis 插件的运行是基于 JDK 动态代理 + 拦截器链实现。
MyBatis 的拦截器可以拦截 4 大对象,开发者通过自定义拦截器,拦截对应的方法实现插件。
- Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler(getParameterObject, setParameters)
- ResultSetHandler(handleResultSets, handleOutputParameters)
- StatementHandler(prepare, parameterize, batch, update, query)
在 MyBatis 的全局配置类 Configuration 中,定义了一个拦截器链。InterceptorChain 中用一个 List 集合保存所有的拦截器,其中 pluginAll 方法会遍历 List 集合,并调用所有拦截器的 plugin 方法。

public class InterceptorChain {
    // 定义了一个List集合装拦截器对象
    private final List<Interceptor> interceptors = new ArrayList();
    public InterceptorChain() {
    }
    // 遍历interceptors,并调用拦截器的plugin方法
    public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }
        return target;
    }
    // 添加拦截器
    public void addInterceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }
    // 获取interceptors
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(this.interceptors);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
MyBatis 的拦截器,自定义插件就需要实现该接口
public interface Interceptor {
    // 拦截处理
    Object intercept(Invocation var1) throws Throwable;
    // 生成代理对象
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    // 插件的属性设置
    default void setProperties(Properties properties) {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
public class Invocation {
    // 目标对象
    private final Object target;
    // 目标对象的方法
    private final Method method;
    // 目标对象的方法参数
    private final Object[] args;
    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    public Object getTarget() {
        return this.target;
    }
    public Method getMethod() {
        return this.method;
    }
    public Object[] getArgs() {
        return this.args;
    }
    // 继续往下执行
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return this.method.invoke(this.target, this.args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# MyBatis 插件的开发
第一步:实现 Interceptor 接口
package com.learn.blog.demo;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Field;
import java.util.Properties;
/**
 * 自定义 MyBatis 插件,拦截 Executor 对象的 query 方法,添加limit 1
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyBatisPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 方法的参数
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        // 获取到原查询sql语句
        String sql = boundSql.getSql();
        // 利用反射修改boundSql的字段
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql + "limit 1");
        // 修改完继续执行
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }
    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
第二步:在 MyBatis 中注册拦截器 mybatis-config.xml
<plugins>
	<plugin interceptor="com.learn.blog.demo.MyBatisPlugin">
        <--可以指定插件的参数-->
		<property />
		<property />
	</plugin>
</plugins>
1
2
3
4
5
6
7
2
3
4
5
6
7
第三步:spring 配置中指定 Mybatis 配置文件路径  
编辑  (opens new window)
  上次更新: 2024/04/19, 08:52:45
- 01
- idea 热部署插件 JRebel 安装及破解,不生效问题解决04-10
- 02
- spark中代码的执行位置(Driver or Executer)12-12
- 03
- 大数据技术之 SparkStreaming12-12
