MyBatis 插件是一种强大的扩展机制,允许开发者在不修改框架源码的情况下,拦截和增强 MyBatis 的核心组件(如 Executor、StatementHandler 等)。以下是 MyBatis 插件的详细解析,包含工作原理、开发步骤及实际应用场景。
一、插件的工作原理
MyBatis 插件基于 动态代理 和 责任链模式 实现,其核心流程如下:
- 拦截目标对象 MyBatis 在启动时,会为目标对象(如
Executor)创建代理对象,代理对象会拦截所有方法调用。 - 责任链调用 若存在多个插件,会按配置顺序形成责任链,依次执行插件的拦截逻辑,最后调用原始方法。
- 动态增强 开发者可以在方法执行前后插入自定义逻辑(如修改 SQL、记录日志等)。
二、插件的开发步骤
1. 实现 Interceptor 接口
自定义插件需实现 Interceptor 接口,并重写以下方法:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target); // 创建代理对象
void setProperties(Properties properties); // 读取配置参数
}
2. 定义拦截目标
使用 @Intercepts 和 @Signature 注解指定要拦截的类、方法及参数类型:
@Intercepts({
@Signature(
type = Executor.class, // 目标类
method = "query", // 目标方法
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class MyPlugin implements Interceptor {
// 实现方法...
}
3. 实现拦截逻辑
在 intercept 方法中编写增强逻辑:
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("Before executing query...");
Object result = invocation.proceed(); // 执行原方法
System.out.println("After executing query.");
return result;
}
4. 注册插件
在 mybatis-config.xml 中添加插件配置:
<plugins>
<plugin interceptor="com.example.MyPlugin">
<property name="param1" value="value1"/> <!-- 可选参数 -->
</plugin>
</plugins>
三、插件的应用场景
1. 分页插件
- 功能:自动为 SQL 添加分页逻辑(如
LIMIT和OFFSET)。 - 拦截点:
StatementHandler.prepare()。 - 示例:MyBatis 官方推荐的
PageHelper插件。
2. SQL 日志打印
- 功能:记录完整 SQL 语句和执行时间。
- 拦截点:
Executor.query()和Executor.update()。
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("SQL执行耗时:" + (end - start) + "ms");
return result;
}
3. 敏感数据脱敏
- 功能:对查询结果中的敏感字段(如手机号、邮箱)进行脱敏处理。
- 拦截点:
ResultSetHandler.handleResultSets()。
4. 多租户数据隔离
- 功能:自动在 SQL 中插入租户 ID 过滤条件。
- 拦截点:
StatementHandler.prepare()。
四、插件的执行顺序与配置
-
执行顺序 插件按配置顺序依次执行。例如,先注册的插件会先被调用。
-
配置参数 通过
<property>标签传递参数到插件:<plugin interceptor="com.example.MyPlugin"> <property name="logLevel" value="DEBUG"/> </plugin>在插件中通过
setProperties读取参数:@Override public void setProperties(Properties properties) { String logLevel = properties.getProperty("logLevel", "INFO"); }
五、注意事项与最佳实践
- 避免过度拦截 仅拦截必要的方法,频繁拦截可能影响性能。
- 线程安全 若插件包含共享状态,需确保线程安全(如使用
ThreadLocal)。 - 兼容性 不同 MyBatis 版本的拦截点可能不同,需参考官方文档。
- 调试技巧 在插件中打印日志或断点调试,验证拦截逻辑是否正确。
六、完整示例:SQL 执行时间监控插件
@Intercepts({
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
public class SqlTimerPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
String methodName = invocation.getMethod().getName();
System.out.println("方法 " + methodName + " 执行耗时:" + (end - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); // 创建代理对象
}
@Override
public void setProperties(Properties properties) {
// 可读取配置参数
}
}
注册插件:
<plugins>
<plugin interceptor="com.example.SqlTimerPlugin"/>
</plugins>
七、总结
MyBatis 插件通过动态代理机制,为开发者提供了灵活的功能扩展能力。无论是性能监控、SQL 增强还是数据脱敏,插件都能在不侵入业务代码的前提下实现。掌握插件的开发与配置,能显著提升 MyBatis 的适应性和可维护性。