在 MyBatis 中,插件机制是通过 Java 的动态代理机制实现的,其核心在于拦截器(Interceptor)和插件注册机制。插件机制可以用于拦截 MyBatis 核心接口的特定方法,从而实现对执行过程的增强和定制。
一、插件作用对象
MyBatis 允许对以下四种对象进行拦截(也是常见的拦截目标接口):
- Executor:执行器,负责数据库操作的核心接口(如增删改查)。
- StatementHandler:封装 JDBC Statement 的操作。
- ParameterHandler:负责 SQL 参数的设置。
- ResultSetHandler:负责结果集的映射。
这些接口的实现类会在执行过程中被包装为代理对象(使用 JDK 动态代理),插件正是通过代理实现方法拦截。
二、插件定义与注册
插件需要实现 org.apache.ibatis.plugin.Interceptor
接口,同时用 @Intercepts
注解声明拦截目标,例如:
@Intercepts({
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object result = invocation.proceed(); // 执行原始方法
// 后置处理
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可配置参数
}
}
注册方式:
<plugins>
<plugin interceptor="com.example.MyPlugin">
<property name="someProperty" value="someValue"/>
</plugin>
</plugins>
三、插件生效时机
插件在以下流程中被应用:
- MyBatis 初始化核心组件(如 Executor、StatementHandler 等)时,会调用
interceptorChain.pluginAll(target)
。 - 在该过程中,依次执行每个插件的
plugin(target)
方法,判断是否匹配,然后返回代理对象。
例如:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target); // 调用每个插件的 plugin()
}
return target;
}
如果当前插件声明要拦截的类型和方法正好匹配 target
,则会包装为代理对象。
四、如何判断是否要代理某个方法?
MyBatis 使用 Plugin.wrap(target, interceptor)
方法来判断是否生成代理对象。
以 Executor 插件为例,MyBatis 会根据 @Signature
注解中配置的元信息创建映射:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
内部维护一个 Map<Class<?>, Set<MethodSignature>>
的结构。判断逻辑如下:
- 当前
target
对象是否实现了@Signature.type
所声明的接口。 - 若实现,是否包含声明的方法名及参数类型。
匹配成功则通过 JDK Proxy 创建代理对象,拦截逻辑会转到 intercept()
方法。
假设插件定义如下:
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
这代表该插件想要拦截 StatementHandler.prepare(Connection, Integer)
方法。
当 RoutingStatementHandler.prepare()
被调用时,若当前对象是代理类,则会先进入插件的 intercept()
方法。
五、执行流程简述
- 调用
pluginAll()
对目标对象应用插件链。 - 每个插件调用
plugin(Object target)
。 - 由
Plugin.wrap()
创建动态代理对象。 - 方法执行时进入
invoke()
,根据拦截签名匹配方法并调用插件的intercept()
。 - 插件内可以执行前置、后置、异常处理等逻辑。
六、代理是如何嵌套的?
如果多个插件都声明拦截 Executor
的某个方法,则会一层层包裹目标对象形成嵌套代理链,执行顺序类似责任链模式:
target = Plugin.wrap(target, interceptor1);
target = Plugin.wrap(target, interceptor2);
...
最终实际调用的是最外层代理,依次进入内部插件。
通过这种方式,MyBatis 实现了插件的灵活扩展能力,适合用于执行日志、权限检查、多租户 SQL 改写、慢 SQL 监控等场景。