插件体系的设计原理:
- 基于拦截器 (Interceptor) 模式: 插件体系的核心是
org.apache.ibatis.plugin.Interceptor接口。我们可以通过实现这个接口来创建自定义插件。 - 拦截点: MyBatis 预定义了四个可以被插件拦截的核心接口(及其方法):
Executor: SQL 执行器,负责 SQL 的最终执行(增删改查、事务提交回滚、缓存交互等)。可拦截update,query,commit,rollback等方法。这是最常用的拦截点之一,比如可以实现分页、分表、读写分离、性能监控等。ParameterHandler: 参数处理器,负责将用户传入的参数对象设置到 JDBC 的PreparedStatement中。可拦截setParameters方法。可以实现参数加密、特殊类型处理等。ResultSetHandler: 结果集处理器,负责将 JDBC 返回的ResultSet映射成 Java 对象。可拦截handleResultSets,handleOutputParameters方法。可以实现结果解密、数据脱敏、自定义类型转换等。StatementHandler: 语句处理器,负责处理 JDBCStatement的操作,包括创建Statement、设置参数、执行 SQL 等。是ParameterHandler和ResultSetHandler的上层。可拦截prepare,parameterize,batch,update,query等方法。常用于 SQL 重写(如物理分页)、SQL 审计等。
- 动态代理 (JDK Dynamic Proxy): MyBatis 插件体系的底层实现依赖于 Java 的动态代理。当 MyBatis 初始化时,如果配置了插件,它会为上述四个核心接口的实例对象创建代理对象。
- 当调用这些核心对象的方法时,实际上是调用了代理对象的方法。
- 代理对象的处理器 (InvocationHandler) 就是插件自身 (
Interceptor实现类)。 - 插件的
intercept方法会被调用,从而获得拦截和修改原始行为的机会。
- 责任链模式 (Chain of Responsibility): 如果配置了多个插件,MyBatis 会按照配置的顺序,将这些插件层层包裹(代理)在原始对象外面,形成一个插件链。调用请求会依次经过链上的每个插件的
intercept方法,最后到达原始对象的方法。
关键接口和注解:
org.apache.ibatis.plugin.Interceptor: 插件必须实现的接口。intercept(Invocation invocation): 核心拦截逻辑。Invocation对象封装了被拦截的目标对象 (target)、目标方法 (Method) 和方法参数 (args)。必须在方法内部调用invocation.proceed()来执行原始方法(或者下一个插件的拦截方法),除非你想完全阻止原始方法的执行。plugin(Object target): 用于生成目标对象的代理对象。通常直接使用 MyBatis 提供的工具类Plugin.wrap(target, this)来创建代理。这个方法会在 MyBatis 构建核心对象时被调用,判断是否需要为target创建代理。setProperties(Properties properties): 用于接收在 MyBatis 配置文件中为该插件配置的属性。
org.apache.ibatis.plugin.Invocation: 封装了拦截点信息的对象,传递给intercept方法。@Intercepts: 标注在Interceptor实现类上,用于声明该插件要拦截哪些方法。它包含一个@Signature数组。@Signature: 标注在@Intercepts内部,用于定义一个具体的拦截签名。包含三个属性:type: 要拦截的接口类型(Executor.class,ParameterHandler.class等)。method: 要拦截的方法名(字符串,如"query","update")。args: 要拦截的方法的参数类型列表(Class<?>[]),用于精确匹配方法签名。
如何编写自定义插件:
以下是编写自定义 MyBatis 插件的基本步骤:
步骤 1:创建插件类并实现 Interceptor 接口
package com.example.myplugin;
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.Properties;
// 1. 使用 @Intercepts 注解声明拦截点
@Intercepts({
// 2. 使用 @Signature 定义具体拦截的方法签名
@Signature(
type = Executor.class, // 拦截 Executor 接口
method = "query", // 拦截 query 方法
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} // query 方法的参数类型列表
),
// 如果需要拦截多个方法,可以添加更多的 @Signature
// @Signature(...)
})
public class MyCustomPlugin implements Interceptor {
private Properties properties = new Properties();
// 3. 实现 intercept 方法,编写核心拦截逻辑
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyCustomPlugin: Before executing " + invocation.getMethod().getName());
// 可以获取原始方法调用的信息
Object target = invocation.getTarget(); // 被拦截的对象 (Executor 实例)
Object[] args = invocation.getArgs(); // 原始方法的参数
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
// === 在执行原始方法前可以做一些事情 ===
// 例如:修改参数、记录日志、权限校验等
System.out.println("SQL ID: " + ms.getId());
System.out.println("Parameter: " + parameter);
// 4. 调用 invocation.proceed() 执行原始方法 (或责任链中的下一个插件)
Object result = invocation.proceed();
// === 在执行原始方法后可以做一些事情 ===
// 例如:修改结果、记录日志、统计耗时等
System.out.println("MyCustomPlugin: After executing " + invocation.getMethod().getName());
System.out.println("Result: " + result); // 注意:result 可能是 List 或 Cursor
// 5. 返回结果 (可能是修改后的结果)
return result;
}
// 6. 实现 plugin 方法,用于生成代理对象
@Override
public Object plugin(Object target) {
// 使用 MyBatis 提供的 Plugin.wrap 方法为目标对象创建代理
// 它会检查当前插件是否配置了拦截该 target 类型的方法
return Plugin.wrap(target, this);
}
// 7. 实现 setProperties 方法,接收配置属性 (可选)
@Override
public void setProperties(Properties properties) {
this.properties = properties;
System.out.println("MyCustomPlugin Properties: " + properties);
// 可以在这里读取插件需要的配置,如:
// String myConfigValue = properties.getProperty("myConfigKey");
}
}
步骤 2:在 MyBatis 配置文件中注册插件
在 mybatis-config.xml 文件中,使用 <plugins> 标签来注册你的自定义插件:
<?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.example.myplugin.MyCustomPlugin">
<!-- 可以为插件传递属性 -->
<property name="someProperty" value="someValue"/>
<property name="anotherProperty" value="anotherValue"/>
</plugin>
<!-- 可以配置多个插件,它们会按顺序形成责任链 -->
<!-- <plugin interceptor="com.example.anotherplugin.AnotherPlugin"/> -->
</plugins>
<!-- 其他配置 ... -->
</configuration>
注意事项:
@Signature的args必须精确匹配: 参数类型和顺序必须与要拦截的方法完全一致,否则插件不会生效。invocation.proceed()的重要性: 忘记调用proceed()会导致原始方法(或后续插件)无法执行。- 性能影响: 插件会增加方法调用的开销,尤其是在插件链较长或
intercept方法逻辑复杂时。需要注意插件对性能的影响。 - 线程安全: 插件实例通常是单例的,会被多个线程共享。如果插件内部有状态,需要确保线程安全。
- 参数修改: 如果在
intercept方法中修改了invocation.getArgs()数组中的参数对象,这个修改会传递给原始方法。 - 结果修改:
intercept方法返回的值会作为最终结果返回给调用者,可以在proceed()之后修改结果。
920

被折叠的 条评论
为什么被折叠?



