MyBatis拦截器原理探究
mybatis拦截器介绍
mybatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截mybatis中的哪些内容呢?
我们进入看一看:
mybatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,mybatis 允许使用插件来拦截的方法调用包括:
- executor (update, query, flushstatements, commit, rollback, gettransaction, close, isclosed)
- parameterhandler (getparameterobject, setparameters)
- resultsethandler (handleresultsets, handleoutputparameters)
- statementhandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法
等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截sql语法构建的处理
拦截器的使用
拦截器介绍及配置
首先我们看下mybatis拦截器的接口定义:
public interface interceptor { object intercept(invocation invocation) throws throwable; object plugin(object target); void setproperties(properties properties); }
比较简单,只有3个方法。 mybatis默认没有一个拦截器接口的实现类,开发者们可以实现符合自己需求的拦截器。
下面的mybatis官网的一个拦截器实例:
@intercepts({@signature( type= executor.class, method = "update", args = {mappedstatement.class,object.class})}) public class exampleplugin implements interceptor { public object intercept(invocation invocation) throws throwable { return invocation.proceed(); } public object plugin(object target) { return plugin.wrap(target, this); } public void setproperties(properties properties) { } }
全局xml配置:
<plugins> <plugin interceptor="org.format.mybatis.cache.interceptor.exampleplugin"></plugin> </plugins>
这个拦截器拦截executor接口的update方法(其实也就是sqlsession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。
源码分析
下面我们分析一下这段代码背后的源码。
首先从源头->配置文件开始分析:
xmlconfigbuilder解析mybatis全局配置文件的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); } } }
具体的解析代码其实比较简单,就不贴了,主要就是通过反射实例化plugin节点中的interceptor属性表示的类。然后调用全局配置类configuration的addinterceptor方法。
public void addinterceptor(interceptor interceptor) { interceptorchain.addinterceptor(interceptor); }
这个interceptorchain是configuration的内部属性,类型为interceptorchain,也就是一个拦截器链,我们来看下它的定义:
public class interceptorchain { 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); } }
现在我们理解了拦截器配置的解析以及拦截器的归属,现在我们回过头看下为何拦截器会拦截这些方法(executor,parameterhandler,resultsethandler,statementhandler的部分方法):
public parameterhandler newparameterhandler(mappedstatement mappedstatement, object parameterobject, boundsql boundsql) { parameterhandler parameterhandler = mappedstatement.getlang().createparameterhandler(mappedstatement, parameterobject, boundsql); parameterhandler = (parameterhandler) interceptorchain.pluginall(parameterhandler); return parameterhandler; } public resultsethandler newresultsethandler(executor executor, mappedstatement mappedstatement, rowbounds rowbounds, parameterhandler parameterhandler, resulthandler resulthandler, boundsql boundsql) { resultsethandler resultsethandler = new defaultresultsethandler(executor, mappedstatement, parameterhandler, resulthandler, boundsql, rowbounds); resultsethandler = (resultsethandler) interceptorchain.pluginall(resultsethandler); return resultsethandler; } public statementhandler newstatementhandler(executor executor, mappedstatement mappedstatement, object parameterobject, rowbounds rowbounds, resulthandler resulthandler, boundsql boundsql) { statementhandler statementhandler = new routingstatementhandler(executor, mappedstatement, parameterobject, rowbounds, resulthandler, boundsql); statementhandler = (statementhandler) interceptorchain.pluginall(statementhandler); return statementhandler; } public executor newexecutor(transaction transaction, executortype executortype, boolean autocommit) { executortype = executortype == null ? defaultexecutortype : executortype; executortype = executortype == null ? executortype.simple : executortype; executor executor; if (executortype.batch == executortype) { executor = new batchexecutor(this, transaction); } else if (executortype.reuse == executortype) { executor = new reuseexecutor(this, transaction); } else { executor = new simpleexecutor(this, transaction); } if (cacheenabled) { executor = new cachingexecutor(executor, autocommit); } executor = (executor) interceptorchain.pluginall(executor); return executor; }
以上4个方法都是configuration的方法。这些方法在mybatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是executor,parameterhandler,resultsethandler,statementhandler(其中parameterhandler和resultsethandler的创建是在创建statementhandler[3个可用的实现类callablestatementhandler,preparedstatementhandler,simplestatementhandler]的时候,其构造函数调用的[这3个实现类的构造函数其实都调用了父类basestatementhandler的构造函数])。
这4个方法实例化了对应的对象之后,都会调用interceptorchain的pluginall方法,interceptorchain的pluginall刚才已经介绍过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象
由于可以拦截statementhandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理statementhandler接口实现类中的sql即可,可使用反射实现。
mybatis还提供了 @intercepts和 @signature关于拦截器的注解。官网的例子就是使用了这2个注解,还包括了plugin类的使用:
@override public object plugin(object target) { return plugin.wrap(target, this); }
下面我们就分析这3个 "新组合" 的源码,首先先看plugin类的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; }
plugin类实现了invocationhandler接口,很明显,我们看到这里返回了一个jdk自身提供的动态代理类。我们解剖一下这个方法调用的其他方法:
getsignaturemap方法:
private static map<class<?>, set<method>> getsignaturemap(interceptor interceptor) { intercepts interceptsannotation = interceptor.getclass().getannotation(intercepts.class); if (interceptsannotation == null) { // issue #251 throw new pluginexception("no @intercepts annotation was found in interceptor " + interceptor.getclass().getname()); } signature[] sigs = interceptsannotation.value(); map<class<?>, set<method>> signaturemap = new hashmap<class<?>, set<method>>(); for (signature sig : sigs) { set<method> methods = signaturemap.get(sig.type()); if (methods == null) { methods = new hashset<method>(); signaturemap.put(sig.type(), methods); } try { method method = sig.type().getmethod(sig.method(), sig.args()); methods.add(method); } catch (nosuchmethodexception e) { throw new pluginexception("could not find method on " + sig.type() + " named " + sig.method() + ". cause: " + e, e); } } return signaturemap; }
getsignaturemap方法解释:首先会拿到拦截器这个类的 @interceptors注解,然后拿到这个注解的属性 @signature注解集合,然后遍历这个集合,遍历的时候拿出 @signature注解的type属性(class类型),然后根据这个type得到带有method属性和args属性的method。由于 @interceptors注解的 @signature属性是一个属性,所以最终会返回一个以type为key,value为set<method>的map。
@intercepts({@signature( type= executor.class, method = "update", args = {mappedstatement.class,object.class})})
比如这个 @interceptors注解会返回一个key为executor,value为集合(这个集合只有一个元素,也就是method实例,这个method实例就是executor接口的update方法,且这个方法带有mappedstatement和object类型的参数)。这个method实例是根据 @signature的method和args属性得到的。如果args参数跟type类型的method方法对应不上,那么将会抛出异常。
getallinterfaces方法:
private static class<?>[] getallinterfaces(class<?> type, map<class<?>, set<method>> signaturemap) { set<class<?>> interfaces = new hashset<class<?>>(); while (type != null) { for (class<?> c : type.getinterfaces()) { if (signaturemap.containskey(c)) { interfaces.add(c); } } type = type.getsuperclass(); } return interfaces.toarray(new class<?>[interfaces.size()]); }
getallinterfaces方法解释:根据目标实例target(这个target就是之前所说的mybatis拦截器可以拦截的类,executor,parameterhandler,resultsethandler,statementhandler)和它的父类们,返回signaturemap中含有target实现的接口数组。
所以plugin这个类的作用就是根据 @interceptors注解,得到这个注解的属性 @signature数组,然后根据每个 @signature注解的type,method,args属性使用反射找到对应的method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。
比如mybatis官网的例子,当configuration调用newexecutor方法的时候,由于executor接口的update(mappedstatement ms, object parameter)方法被拦截器被截获。因此最终返回的是一个代理类plugin,而不是executor。这样调用方法的时候,如果是个代理类,那么会执行:
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); } }
没错,如果找到对应的方法被代理之后,那么会执行interceptor接口的interceptor方法。
这个invocation类如下:
public class invocation { private object target; private method method; private object[] args; public invocation(object target, method method, object[] args) { this.target = target; this.method = method; this.args = args; } public object gettarget() { return target; } public method getmethod() { return method; } public object[] getargs() { return args; } public object proceed() throws invocationtargetexception, illegalaccessexception { return method.invoke(target, args); } }
它的proceed方法也就是调用原先方法(不走代理)。
总结
mybatis拦截器接口提供的3个方法中,plugin方法用于某些处理器(handler)的构建过程。interceptor方法用于处理代理类的执行。setproperties方法用于拦截器属性的设置。
其实mybatis官网提供的使用 @interceptors和 @signature注解以及plugin类这样处理拦截器的方法,我们不一定要直接这样使用。我们也可以抛弃这3个类,直接在plugin方法内部根据target实例的类型做相应的操作。
总体来说mybatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对mybatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点。
总结
以上所述是小编给大家介绍的mybatis拦截器原理探究,希望对大家有所帮助
上一篇: C语言二分查找
下一篇: [是题解哦] 洛谷 P1342 请柬