您好,欢迎访问一九零五行业门户网

【MyBatis源码分析】插件实现原理

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源码分析】插件实现原理的详细内容。
其它类似信息

推荐信息