mybatis插件研究

最近在做jf的时候,因为后台程序用到了mybatis3,这样就无法避免的涉及到了mybatis的分页,下面是学习的结果:

研究起因:

  因为mybatis3的分页是内存分页,也就是说把全部数据加载进来,在根据传进来RowBounds的数据信息进行分页。

  这样调用

List<User> users1 = ss.selectList("user.getUser",null,new RowBounds(0,1));

的时候传入RowBounds进行内存分页,显然这样碰到大数据量的话是不行的。是在DefaultResultHandler进行处理的时候进行

分页的

private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
      }
    }
  }

 

他会判断你的结果集是不是只读的,如果是只读的就进行next调到offset,如果是可定位的就进行absolute,这样对于大数据量的

数据太可怕了(主要是数据库方面的性能,对于数据量小的话,也许直接用rs的next offset性能更加,这个没有仔细测试过)。网上搜索相关资料,可以通过mybatis的插件实现,对于mybatis的物理分页这个后续的文章会说到。

开始研究:

  查看mybatis3的源码开始,在eclipse导入mybatis的源码,可以很清楚的看到plugins

  打开各个类都不大,可以很清楚的看清楚。其中intercepts和sinature是两个注解,其中intercepts的注解内容是Signature的

数组,Signature的三个属性是type,method,args

刚开始看的时候可能不清楚这三个属性具体意思,带着疑问看Plugins类,Plugins类是重点,代开Plugins类看到他实现了InvocationHandler接口,

似乎事情明朗了一些,看到InvocationHandler这个接口可以联想到Proxy.newProxy(ClassLoader,calss[],invocationhandler(){}),大概扫描

一下这个类的方法,看到了如下代码:

return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));

果然mybatis的插件机制就是通过jdk的动态代理实现的,现在应该具体仔细看下Plugin类的代码了,有三个字段

private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;

构造函数是私有的,不能new

private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

有两个public的方法一个叫wrap是个static方法,一个叫invoke,这个是invocationhandler接口方法,当要被拦截的方法执行的

时候执行的方法。看wrap这个静态方法,他调用了两个私有方法getSignatureMap,getAllInterfaces。

仔细研究这两个方法,其中getSignatureMap方法的作用是手机interceptor对象上的Intercepts注解的信息,将其中的type和method属性分装成

hashmap<type,method>。再看getAllInterfaces方法,他接受getSignatureMap方法的返回值也就是hashmap<type,method>作为参数

判断target实现的接口的type在不在hashmap<type,method>的key中。这个判断很重要,直接导致了interceptor只拦截自己type指定的类。

回到wrap方法

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;
  }

如果通过getAllInterfaces得到的接口interfaces的length大于0返回代理,如果为0则返回target本身,这不废话吗?其实意思就是说只拦截interceptor指定

的类。

现在Plugin类看完了,但是感觉Plugin包里面的类的功能是独立于mybatis3的呢,没有相关性啊,想到mybatis的主配置文件里面有Plugins的配置选项,

<plugins>
        <plugin interceptor="interceptors.TestInterceptor"></plugin>
        <plugin interceptor="interceptors.PagingInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

想到了解析和分装mybatis主配置信息的两个类XMLConfigBuilder和Configuration,在XMLConfigBuilder里面看到了方法pluginElement

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);
      }
    }
  }

看到configuration.addInterceptor(interceptorInstance)这行代码

ctrl+leftmouse进去

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

于是去查看Plugin包下面的InterceptorChain类,有三个方法:

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);
  }

pluginAll方法里面的内容,循环用interceptor处理target,想到了我们实现的interceptor的plugin方法应该调用

Plugin.wrap方法进行处理,得到代理类,但是pluginAll这个什么时候被调用呢,于是用eclipse功能查看哪些方法调用

了pluginAll类,如下:

恍然大悟,原来如此。

结论:

当我们在mybatis的配置文件里配置了我们写的interceptor的时候,xmlconfigurationbuilder将他解析进入configuration

类里面,存在intercepterChain里面,当调用newExecutor,newParameterHandler,newResultSetHandler和newStatementHandler

这几个方法的时候会调用pluginAll方法,而pluginAll方法会调用我们实现的interceptor,而plugin方法里面我们的代码是Plugin.Wrap传入的是

target和interceptor,这个tareget可以是executor等实现类,parameterhandler,resultsethandler,statementhandler,也就是我们可以

修改这几个类的默认行为,调用plugin方法生成代理类,当调用这些类里面的方法的时候,如果配置了这个类的interceptor,proxy将产生作用。

完毕,谢谢!

 

posted @ 2013-10-31 20:52  tom是cat  阅读(1551)  评论(0)    收藏  举报