MyBatis插件的原理

4 篇文章 0 订阅

MyBatis最终对数据库操作的是内部框架定义的StatementHandler接口(可以参考本人文章http://blog.csdn.net/wq_tft/article/details/40134209),分页插件实现的原理就是对这个StatementHandler进行加工,利用的是java的动态代理机制,也就是说最终这个StatementHandler是个代理对象。

MyBatis的插件都必须实现org.apache.ibatis.plugin.Interceptor接口,该接口的定义如下:

package org.apache.ibatis.plugin;

import java.util.Properties;

public interface Interceptor {
  // 实际执行的代理方法
  Object intercept(Invocation invocation) throws Throwable;
  // 获取代理对象,target是源对象,也就是StatementHandler
  Object plugin(Object target);
  // 设置属性,可以不用care
  void setProperties(Properties properties);

}
那如何获得代理对象呢,也就是实现插件的接口中的plugin方法?MyBatis有个默认的静态方法可以获取:org.apache.ibatis.plugin.Plugin.wrap(Object target, Interceptor interceptor),源代码如下:

  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这个类实现了InvocationHandler接口,MyBatis实际上就是对StatementHandler的源实现对象进行代理,然后实际上的触发方法是Plugin的invoke(Object proxy, Method method, Object[] args)方法,在Plugin的构造函数中传入了源目标对象(也就是StatementHandler),interceptor(也就是我们实现的插件),signatureMap(根据插件类的注解,判断对StatementHandler的哪些方法进行拦截),这些信息作为Plugin的成员对象保存下来。

Plugin的invoke方法源代码如下:

/**
*proxy:被代理的对象,即StatementHandler
*method:被代理对象触发的方法
*args:被代理对象触发方法的传入参数
**/
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);
    }
  }
从源代码里面可以看出,MyBatis会先判断被代理对象的方法是否需要拦截,如果不需要拦截,直接触发源对象的方法即可,即
method.invoke(target, args)
否则,则触发插件的intercept方法,这个方法的参数传入了Invocation对象,在这个对象里面保存了被代理的对象,被代理对象的执行方法,以及被代理对象执行方法的传入参数。

然后在插件里面,我们可以根据Invocation这个对象里面的信息,获取原来要执行的对象(StatementHandler,实际上是RoutingStatementHandler,RoutingStatementHandler的delegate成员是实际操作数据库的StatementHandler)的信息,利用java的反射机制,我们可以随意的修改其中的任意信息(需要对源代码有一定理解),比如sql语句等等,进行分页语句的加工,然后执行Invocation.proceed()方法,触发源对象的方法,return源对象的返回值。原理就是是源对象中的信息已经被我们改变了,所以它实际查询出来的数据就是我们想要的分页的数据集,这就达到了插件的目的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值