当前位置: 移动技术网 > IT编程>开发语言>Java > MyBatis拦截器原理探究

MyBatis拦截器原理探究

2019年07月19日  | 移动技术网IT编程  | 我要评论

mybatis拦截器介绍

mybatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截mybatis中的哪些内容呢?

我们进入看一看:

mybatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,mybatis 允许使用插件来拦截的方法调用包括:

  1. executor (update, query, flushstatements, commit, rollback, gettransaction, close, isclosed)
  2. parameterhandler (getparameterobject, setparameters)
  3. resultsethandler (handleresultsets, handleoutputparameters)
  4. statementhandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法

等。

总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截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拦截器原理探究,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网