Mybatis插件

     在开篇之前一定要多说一句,再没有弄清楚插件的时候去使用插件是非常危险的,使用插件就意味着改变底层的封装,它给予我们灵活性的同时也给了我们毁灭mybatis框架的可能性,操作不当有可能摧毁Mybatis框架,只有掌握了Mybatis的四大对象的协作过程,和插件的实现原理擦能构建出安全高效的插件,所以我们再次强调一下,再没有弄清楚插件之前能不使用就不要使用插件,谨慎使用。

1.插件接口

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}
插件接口定义类三个方法,先来了解一下三个方法的含义:
intercept方法,它将直接覆盖所拦截的所有的方法,因为他是插件的核心方法。这个方法里面有个参数Invocation对象,通过它可以反射调度原来的对象方法,稍后讨论其设计和使用。
plugin方法,target是被拦截的对象,它的作用是给被拦截的对象一个生成一个代理对象,并返回它, 为了方便mybatis使用Plugin中的warp静态方法提供生成代理对象,我们往往使用plugin方法就可以生成一个代理对象,当然也可以自定义去实现,这时候要小心。
setProperties方法,允许plugin元素中配置所需要的参数,方法在插件初始化的时候就被调用一次,然后把对象存入到配置中以便以后再取出。
    分析一下可以看出来这部分内容使用的是模板模式,就是提供一个骨架定义了方法,以及方法执行的功能,其它的由开发者来完成。

2.插件的初始化

插件的初始化是在mybatis初始化的时候完成的,我们可以到XMLConfgiBuilder中的代码了解一下:
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
     在配置文件的时候,初始化mybatis开始读入插件节点和我们配置的参数,同时使用反射技术生成对应的插件实例,然后调用插件方法中的setProperties方法设置配置的参数,然后插件实例被保存到配置对象中,便于读取和使用,所以插件实例是一开始就被初始化的,而不是被用到的时候采取初始化的,我们使用它的时候直接取出来使用就可以了,这样有助于性能的提高。
     再看一下Configuration对象是如何保存的:
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
     通过interceptorChain在Configuration里面是一个属性,其有个addInterceptor方法,看一下这个方法是如何实现的;
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

     可以看到其声明了一个List,通过调用其add方法,来存储生成的interceptor对象。显然,完成初始化的插件就保存在这个List对象里面等待其被取出使用。

3.插件的代理和反射使用

     其实你也能猜到,其实插件使用的是责任链模式,就是一个对象在mybatis中可能是四大对象中的一个,在多个角色中传递,处在传递链上的任何角色都有处理它的机会,这个有点抽象,直接点就是说我们的请假流程,需要一级一级的报告批准才可以,这个有点像spring框架中的bean的初始化方法调用的过程。主要的意思就是说这个对象可以让这条处理的链子中任何一个对象都可以拦截请求对象,进行处理。
     mybatis中的责任链是有interceptorChain来定义的,其实可以联想到,我们在创建执行器的时候使用过这样的代码:
   可以看到我们创建执行器的时候已使用到了这个plugin相关的内容,接着再看一下,更清楚的看一下这个pluginAll方法是如何实现的:
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
    我们了解到,plugin方法是生成代理对象的方法,当其取出插件的时候是从Configuration对象中去取出的。从第一个一直到最后一个,这里指的是四大对象,将对象传递给了plugin方法,然后返回一个代理对象,如果存在第二个插件,我们到时候就会取到第二个代理对象,以此类推,有多少个拦截器就有多少个代理对象,这样一个插件就可以拦截到真是的对象了。这就好比每一个插件都可以一层层处理被拦截的对象,其实,仔细观察的话也会发现mybatis的四大对象也是这洋执行的。
     如果自己编写工具类会比较麻烦,所以我们提供了一个常用的工具类,用来生成代理对象,他是plugin类,其实现了InvocationHandler接口,也就说明此类是基于JDK的动态代理的方式实现的,接下来看一个重要的方法:
  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;
  }

  @Override
  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);
    }
  }
    可以看到它是一个动态代理对象,其中wrap方法为我们生成这个对象的动态代理对象,紧接着invoke方法会将使用这个类生成代理对象,吗们代理对象再调用方法的时候就会进入到invoke方法,如果存在签名的拦截方法,插件的intercept方法就会被我们在这里调用,然后就返回结果。
     首先创建一个Invocation对象,其构造方法的参数包括被代理的对象、及参数。Invocation对象进行初始化,其有i一个proceed()方法,如下面的代码所示:
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

    这个方法就是调度被代理对象的真实方法,现在假设有n个插件,我们知道第一个传递的参数是四大对象的本身,然后调用一次wrap方法产生的第一个代理对象,而这里反射的反射四大对象的真实方法,如果,再有,则重复上面的过程,最后四大对象本身的方法也会被调用,只是它会从最后一个代理对象的invoke方法运行到第一个代理对象的invoke方法,直到四大对象的真实方法。
     然而,我们一个个加载插件的实例的时候,应用setProperties()方法进行初始化,我们可以使用mybatis提供的Plugin中的wrap方法生成代理对象,在一层使用Invocation对象的proceed方法来推动代理对象的运行,所以在多个插件的环境下,调度proceed方法时,mybatis总是从最后一个对象运行到第一个代理对象,最后是真实的被拦截的对象方法被运行。

4.常用的工具类--MetaObject
   这个类可以有限的读取或者修改一些重要对象的属性,在mybatis中四大对象给我们提供的public设置参数的方法很少,我们难以通过其自身的到相关的属性信息,单数有了MetaObject这个工具类,就可以通过其他的技术手段都去或者修改这些重要对象的属性,在mybatis中他是一个很常用的类;
     经常使用的有三个方法:
 MetaObject forObject(Object object、ObjectFactory objectFactory、ObjectWarpperFacytory objectWrapperFactory)方法用于包装对象,这个方法不再使用了,而是mybatis为我们提供了SystemMetaObject.forObject(Object obj)
Object getValue(String name) 方法用于获取对象属性值,支持OGNL。
void setValue(String name,Object value)用于修属性值,支持OGNL。
    拦截StatementHandler对象,我们需要首先获取他要执行的SQL语句,并修改一些值,这时候可以使用MetaObject。
其实我们拦截的StatementHandler实际上是RoutingStatementHandler对象,它的delegate属性才是真是服务的statementHandler,真实的StatementHandler有一个属性,BoundSql,它下面有一个sql属性,所以才有了路径delegate.boundSql.sql。我们可以通过这个路径去获取或者修改对应运行时的SQL。通过改写就可以限制查询的sql都只能至多返回1000行记录。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值