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将产生作用。
完毕,谢谢!
浙公网安备 33010602011771号