Mybatis插件机制的原理

在 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() 方法。


五、执行流程简述

  1. 调用 pluginAll() 对目标对象应用插件链。
  2. 每个插件调用 plugin(Object target)
  3. Plugin.wrap() 创建动态代理对象。
  4. 方法执行时进入 invoke(),根据拦截签名匹配方法并调用插件的 intercept()
  5. 插件内可以执行前置、后置、异常处理等逻辑。

六、代理是如何嵌套的?

如果多个插件都声明拦截 Executor 的某个方法,则会一层层包裹目标对象形成嵌套代理链,执行顺序类似责任链模式:

target = Plugin.wrap(target, interceptor1);
target = Plugin.wrap(target, interceptor2);
...

最终实际调用的是最外层代理,依次进入内部插件。



通过这种方式,MyBatis 实现了插件的灵活扩展能力,适合用于执行日志、权限检查、多租户 SQL 改写、慢 SQL 监控等场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值