mybatis插件原理----从<plugins>解析开始
本文分析一下mybatis的插件实现原理,在此之前,如果对mybatis插件不是很熟悉的朋友,可参看此文mybatis7:mybatis插件及示例----打印每条sql语句及其执行时间,本文我以一个例子说明了mybatis插件是什么以及如何实现。由于mybatis的插件已经深入到了mybatis底层代码,因此要更好地使用插件,必须对插件实现原理及mybatis底层代码有所熟悉才行,本文分析一下mybatis的插件实现原理。
首先,我们从插件<plugins>解析开始,源码位于xmlconfigbuilder的pluginelement方法中:
1 private void pluginelement(xnode parent) throws exception { 2 if (parent != null) { 3 for (xnode child : parent.getchildren()) { 4 string interceptor = child.getstringattribute(interceptor); 5 properties properties = child.getchildrenasproperties(); 6 interceptor interceptorinstance = (interceptor) resolveclass(interceptor).newinstance(); 7 interceptorinstance.setproperties(properties); 8 configuration.addinterceptor(interceptorinstance); 9 }10 }11 }
这里拿<plugin>标签中的interceptor属性,这是自定义的拦截器的全路径,第6行的代码通过反射生成拦截器实例。
再拿<plugin>标签下的所有<property>标签,解析name和value属性成为一个properties,将properties设置到拦截器中。
最后,通过第8行的代码将拦截器设置到configuration中,源码实现为:
1 public void addinterceptor(interceptor interceptor) { 2 interceptorchain.addinterceptor(interceptor); 3 }
interceptorchain是一个拦截器链,存储了所有定义的拦截器以及相关的几个操作的方法:
1 public class interceptorchain { 2 3 private final list<interceptor> interceptors = new arraylist<interceptor>(); 4 5 public object pluginall(object target) { 6 for (interceptor interceptor : interceptors) { 7 target = interceptor.plugin(target); 8 } 9 return target;10 }11 12 public void addinterceptor(interceptor interceptor) {13 interceptors.add(interceptor);14 }15 16 public list<interceptor> getinterceptors() {17 return collections.unmodifiablelist(interceptors);18 }19 20 }
分别有添加拦截器、为目标对象添加所有拦截器、获取当前所有拦截器三个方法。
mybatis插件原理----pluginall方法添加插件
上面我们在interceptorchain中看到了一个pluginall方法,pluginall方法为目标对象生成代理,之后目标对象调用方法的时候走的不是原方法而是代理方法,这个在后面会说明。
mybatis官网文档有说明,在以下四个代码执行点上允许使用插件:
为之生成插件的时机(换句话说就是pluginall方法调用的时机)是executor、parameterhandler、resultsethandler、statementhandler四个接口实现类生成的时候,每个接口实现类在mybatis中生成的时机是不一样的,这个就不看它们是在什么时候生成的了,每个开发工具我相信都有快捷键可以看到pluginall方法调用的地方,我使用的eclipse就是ctrl+alt+h。
再看pluginall方法:
1 public object pluginall(object target) {2 for (interceptor interceptor : interceptors) {3 target = interceptor.plugin(target);4 }5 return target;6 }
这里值得注意的是:
形参object target,这个是executor、parameterhandler、resultsethandler、statementhandler接口的实现类,换句话说,plugin方法是要为executor、parameterhandler、resultsethandler、statementhandler的实现类生成代理,从而在调用这几个类的方法的时候,其实调用的是invocationhandler的invoke方法
这里的target是通过for循环不断赋值的,也就是说如果有多个拦截器,那么如果我用p表示代理,生成第一次代理为p(target),生成第二次代理为p(p(target)),生成第三次代理为p(p(p(target))),不断嵌套下去,这就得到一个重要的结论:<plugins>...</plugins>中后定义的<plugin>实际其拦截器方法先被执行,因为根据这段代码来看,后定义的<plugin>代理实际后生成,包装了先生成的代理,自然其代理方法也先执行
plugin方法中调用mybatis提供的现成的生成代理的方法plugin.wrap(object target, interceptor interceptor),接着我们看下wrap方法的源码实现。
mybatis插件原理----plugin的wrap方法的实现
plugin的wrap方法实现为:
1 public static object wrap(object target, interceptor interceptor) { 2 map<class<?>, set<method>> signaturemap = getsignaturemap(interceptor); 3 class<?> type = target.getclass(); 4 class<?>[] interfaces = getallinterfaces(type, signaturemap); 5 if (interfaces.length > 0) { 6 return proxy.newproxyinstance( 7 type.getclassloader(), 8 interfaces, 9 new plugin(target, interceptor, signaturemap));10 }11 return target;12 }
首先看一下第2行的代码,获取interceptor上定义的所有方法签名:
1 private static map<class<?>, set<method>> getsignaturemap(interceptor interceptor) { 2 intercepts interceptsannotation = interceptor.getclass().getannotation(intercepts.class); 3 // issue #251 4 if (interceptsannotation == null) { 5 throw new pluginexception(no @intercepts annotation was found in interceptor + interceptor.getclass().getname());
6 } 7 signature[] sigs = interceptsannotation.value(); 8 map<class<?>, set<method>> signaturemap = new hashmap<class<?>, set<method>>(); 9 for (signature sig : sigs) {10 set<method> methods = signaturemap.get(sig.type());11 if (methods == null) {12 methods = new hashset<method>();13 signaturemap.put(sig.type(), methods);14 }15 try {16 method method = sig.type().getmethod(sig.method(), sig.args());17 methods.add(method);18 } catch (nosuchmethodexception e) {19 throw new pluginexception(could not find method on + sig.type() + named + sig.method() + . cause: + e, e);20 }21 }22 return signaturemap;23 }
看到先拿@intercepts注解,如果没有定义@intercepts注解,抛出异常,这意味着使用mybatis的插件,必须使用注解方式。
接着拿到@intercepts注解下的所有@signature注解,获取其type属性(表示具体某个接口),再根据method与args两个属性去type下找方法签名一致的方法method(如果没有方法签名一致的就抛出异常,此签名的方法在该接口下找不到),能找到的话key=type,value=set<method>,添加到signaturemap中,构建出一个方法签名映射。举个例子来说,就是我定义的@intercepts注解,executor下我要拦截的所有method、statementhandler下我要拦截的所有method。
回过头继续看wrap方法,在拿到方法签名映射后,调用getallinterfaces方法,传入的是target的class对象以及之前获取到的方法签名映射:
1 private static class<?>[] getallinterfaces(class<?> type, map<class<?>, set<method>> signaturemap) { 2 set<class<?>> interfaces = new hashset<class<?>>(); 3 while (type != null) { 4 for (class<?> c : type.getinterfaces()) { 5 if (signaturemap.containskey(c)) { 6 interfaces.add(c); 7 } 8 } 9 type = type.getsuperclass();10 }11 return interfaces.toarray(new class<?>[interfaces.size()]);12 }
这里获取target的所有接口,如果方法签名映射中有这个接口,那么添加到interfaces中,这是一个set,最终将set转换为数组返回。
wrap方法的最后一步:
1 if (interfaces.length > 0) {2 return proxy.newproxyinstance(3 type.getclassloader(),4 interfaces,5 new plugin(target, interceptor, signaturemap));6 }7 return target;
如果当前传入的target的接口中有@intercepts注解中定义的接口,那么为之生成代理,否则原target返回。
这段理论可能大家会看得有点云里雾里,我这里举个例子:
= statementhandler., method = query, args = {statement., resulthandler.= statementhandler.,
method = update, args = {statement. org.apache.ibatis.executor.statement.statementhandler=[ org.apache.ibatis.executor.statement.statementhandler.update(java.sql.
statement) java.sql.sqlexception, java.util.list org.apache.ibatis.executor.statement.statementhandler.query(java.sql.statement,org.apache.
ibatis.session.resulthandler) java.sql.sqlexception]}
一个class对应一个set,class为statementhandler.class,set为stataementhandler中的两个方法
如果我new的是statementhandler接口的实现类,那么可以为之生成代理,因为signaturemap中的key有statementhandler这个接口
如果我new的是executor接口的实现类,那么直接会把executor接口的实现类原样返回,因为signaturemap中的key并没有executor这个接口
相信这么解释大家应该会明白一点。注意这里生不生成代理,只和接口在不在@intercepts中定义过有关,和方法签名无关,具体某个方法走拦截器,在invoke方法中,马上来看一下。
mybatis插件原理----plugin的invoke方法
首先看一下plugin方法的方法定义:
1 public class plugin implements invocationhandler { 2 3 private object target; 4 private interceptor interceptor; 5 private map<class<?>, set<method>> signaturemap; 6 7 private plugin(object target, interceptor interceptor, map<class<?>, set<method>> signaturemap) { 8 this.target = target; 9 this.interceptor = interceptor;10 this.signaturemap = signaturemap;11 }12 ...13 }
看到plugin是invocationhandler接口的实现类,换句话说,为目标接口生成代理之后,最终执行的都是plugin的invoke方法,看一下invoke方法的实现:
1 public object invoke(object proxy, method method, object[] args) throws throwable { 2 try { 3 set<method> methods = signaturemap.get(method.getdeclaringclass()); 4 if (methods != null && methods.contains(method)) { 5 return interceptor.intercept(new invocation(target, method, args)); 6 } 7 return method.invoke(target, args); 8 } catch (exception e) { 9 throw exceptionutil.unwrapthrowable(e);10 }11 }
在这里,将method对应的class拿出来,获取该class中有哪些方法签名,换句话说就是executor、parameterhandler、resultsethandler、statementhandler,在@intercepts注解中定义了要拦截哪些方法签名。
如果当前调用的方法的方法签名在方法签名集合中,即满足第4行的判断,那么调用拦截器的intercept方法,否则方法原样调用,不会执行拦截器。
以上就是【mybatis源码分析】插件实现原理的详细内容。