文档章节

深入浅出Mybatis-插件原理

袁继峰
 袁继峰
发布于 2016/03/14 13:38
字数 1531
阅读 9
收藏 0

原文链接:http://blog.csdn.net/hupanfeng/article/details/9247379


Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

代理链的生成

Mybatis支持对ExecutorStatementHandlerPameterHandlerResultSetHandler进行拦截,也就是说会对这4种对象进行代理。下面以Executor为例。Mybatis在创建Executor对象时会执行下面一行代码:

[java] view plain copy

  1. executor =(Executor) interceptorChain.pluginAll(executor);  

InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。上面这句代码的含义是调用拦截器链里的每个拦截器依次对executor进行plugin(插入?)代码如下:

[java] view plain copy

  1.  /** 

  2.   * 每一个拦截器对目标类都进行一次代理 

  3.   * @paramtarget 

  4.   * @return 层层代理后的对象 

  5.   */  

  6.  public ObjectpluginAll(Object target) {  

  7.      for(Interceptor interceptor : interceptors) {  

  8.          target= interceptor.plugin(target);  

  9.      }  

  10.      returntarget;  

  11. }  

下面以一个简单的例子来看看这个plugin方法里到底发生了什么。

[java] view plain copy

  1. @Intercepts({@Signature (type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})  

  2. public class ExamplePlugin implements Interceptor {  

  3.     @Override  

  4.     public Objectintercept(Invocation invocation) throws Throwable {  

  5.         returninvocation.proceed();  

  6.     }  

  7.   

  8.     @Override  

  9.     public Objectplugin(Object target) {  

  10.         returnPlugin.wrap(target, this);  

  11.     }  

  12.   

  13.     @Override  

  14.     public voidsetProperties(Properties properties) {  

  15.     }  

  16. }  



每一个拦截器都必须实现上面的三个方法,其中:

1)       Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

2)       Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。

3)       setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名  [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

Plugin.wrap方法

从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

[java] view plain copy

  1. public staticObject wrap(Object target, Interceptor interceptor) {  

  2.    //从拦截器的注解中获取拦截的类名和方法信息  

  3.    Map<Class<?>, Set<Method>> signatureMap =getSignatureMap(interceptor);  

  4.    Class<?> type = target.getClass();  

  5.    //解析被拦截对象的所有接口(注意是接口)  

  6.    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);  

  7.    if(interfaces.length > 0) {  

  8.         //生成代理对象, Plugin对象为该代理对象的InvocationHandler  (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念)  

  9.         returnProxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));  

  10.     }  

  11.     returntarget;  

  12. }   

 这个Plugin类有三个属性:

   private Object target;//被代理的目标类

   private Interceptor interceptor;//对应的拦截器

   private Map<Class<?>, Set<Method>> signatureMap;//拦截器拦截的方法缓存

我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对

executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。

代理链上的拦截

代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,他的invoke方法是怎么样的呢?

[java] view plain copy

  1. public Objectinvoke(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.             //调用代理类所属拦截器的intercept方法,  

  6.            return interceptor.intercept(new Invocation(target, method, args));  

  7.         }  

  8.         returnmethod.invoke(target, args);  

  9.     } catch(Exception e) {  

  10.         throwExceptionUtil.unwrapThrowable(e);  

  11.     }  

  12.    

invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个Invocation对象,这个对象是什么样子的,他的功能又是什么呢?

[java] view plain copy

  1. public class Invocation {  

  2.   

  3.   private Object target;  

  4.   private Methodmethod;  

  5.   private Object[]args;  

  6.    

  7.   publicInvocation(Object target, Method method, Object[] args) {  

  8.     this.target =target;  

  9.     this.method =method;  

  10.     this.args =args;  

  11.   }  

  12.   ...  

  13.   

  14.   public Objectproceed() throws InvocationTargetException, IllegalAccessException {  

  15.     returnmethod.invoke(target, args);  

  16.   }  

  17. }  



可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是returninvocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。

[java] view plain copy

  1. public Object intercept(Invocation invocation) throws Throwable {  

  2.   

  3.      //完成代理类本身的逻辑  

  4.      ...  

  5.      //通过invocation.proceed()方法完成调用链的推进  

  6.      return invocation.proceed();  

  7.   }   

总结

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

1)       所有可能被拦截的处理类都会生成一个代理

2)       处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

3)       执行插接中的拦截方法后,推进目标的执行

如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

因此,在编写插件时需注意以下几个原则:

1)       不编写不必要的插件;

2)       实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本省,这样可以减少目标被代理的次数。


本文转载自:http://blog.csdn.net/hupanfeng/article/details/9247379

共有 人打赏支持
袁继峰
粉丝 1
博文 9
码字总数 7317
作品 0
海淀
高级程序员
私信 提问
深入浅出MyBatis_Index

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/22
932
0
深入浅出MyBatis-快速入门

深入浅出MyBatis-快速入门 深入浅出MyBatis-Configuration 深入浅出MyBatis-MapperBuilder 深入浅出MyBatis-Sqlsession 深入浅出Mybatis-插件原理 深入浅出Mybatis-分页 深入浅出Mybatis-sql...

perfectspr
2014/11/26
0
1
【深入浅出MyBatis系列九】改造Cache插件

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/25
624
0
【深入浅出MyBatis系列八】SQL自动生成插件

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/25
818
1
【深入浅出MyBatis系列七】分页插件

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/24
1K
1

没有更多内容

加载失败,请刷新页面

加载更多

js数组遍历和对象遍历

数组遍历 for for(var i=0,len=arr.length;i<len;i++){console.log(arr[i]);} forEach - ES5语法,性能比for弱,不能使用break终止循环,不能使用return arr.forEach(function(item,inde......

祖达
25分钟前
2
0
Java网络编程

基本概念 网络IO会涉及到同步,异步,阻塞,非阻塞等几个概念。 一个网络IO读取过程是数据从 网卡 到 内核缓冲区 到 用户内存 的过程。同步和异步区别在于数据从内核到用户内存的过程是否需要...

春哥大魔王的博客
46分钟前
2
0
Spring "reg:zookeeper" 的前缀 "reg" 未绑定等类似问题解决方案。

今天同事遇到一个Spring启动加载配置文件时,不识别reg:zookeeper标签的问题。 我查看配置,发现是Spring配置文件的头部没有引入reg标签的命名空间,具体如下图: 所以,以后遇到类似的标签未...

花漾年华
今天
2
0
阿里云领衔云市场

近期,2018年Q4及全年的全球云基础设施服务市场数据新鲜出炉,发布方是美国市场研究机构Synergy Research Group。这个机构是专做电信网络市场情报的公司,成立于1999年,每年都会公布各大公有...

linuxCool
今天
2
0
C++友元函数和友元类(C++ friend)详解

私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这固然能够带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书...

shzwork
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部