Mybatis源码分析十二之Plugin插件

一、插件

Mybatis中允许我们在执行过程中,在一些节点处进行拦截,具体可以在如下对象的方法中进行拦截:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

先来个demo,要使用插件功能只需要实现Interceptor 接口,指定需要拦截的方法签名即可,并在配置文件中配置好自定义的插件。

<plugins>
  <plugin interceptor="xxx.xxx.XXXPlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

假设我们需要统计Executor 的query方法的调用次数,首先我们需要实现Interceptor 接口,然后定义好query方法的方法签名:

@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class QueryCountPlugin implements Interceptor {
  private int count;
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("query times:"+count++);
    return invocation.proceed();
  }
}

然后在全局配置文件中配置好插件:

    <plugins>
        <plugin interceptor="org.apache.ibatis.QueryCountPlugin"></plugin>
    </plugins>

此时,只要有Executor 的query方法调用,就会不断的输出调用次数,所以插件的实现就是这么的简单,完全可以根据自身的需求去做定制化功能。

二、源码实现

第一部分简单了解了插件的使用,这部分我们将从源码中去探讨他的实现过程,mybatis中支持拦截的对象有Executor 、StatementHandler 、ResultSetHandler 、ParameterHandler 这四种对象。整体的实现逻辑是一致的,再次我们以Executor 的拦截作为分析对象。

之前分析可知,Executor 的实例化在SqlSessionFactory的openSession中,实例化完成后有这么一段调用。

 executor = (Executor) interceptorChain.pluginAll(executor)

interceptorChain是Configuration对象中的一个InterceptorChain属性,里边有个保存Interceptor的集合interceptors。

  public Object pluginAll(Object target) {
  //从插件集合中不断取出对应插件,包装目标对象
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

在分析Configuration解析时,我们知道配置好的插件会保存在interceptors集合中,所以此时我们需要查看Interceptor 接口的默认实现方法plugin。

  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

Plugin类实现了InvocationHandler接口,所以插件的实现,也是通过动态代理。

  public static Object wrap(Object target, Interceptor interceptor) {
  //获取拦截对象和对应的拦截方法的集合
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //获取拦截对象的所有接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
    //返回代理对象
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

以上是动态代理的一个实现过程,具体的逻辑在于Plugin的invoke方法。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //这里就是判断当前执行的方法是不是需要拦截的方法
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

所以插件的整体实现逻辑比较简单,剩下的对象中也只需执行interceptorChain.pluginAll()方法即可。

三、总结

mybatis的插件功能在提供方便的同时,也存在一定的风险,因为插件的使用都是对底层调用的封装,如果调用不当就会导致底层业务逻辑错误。

以上,有任何不对的地方,请指正,敬请谅解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值