深入浅出Mybatis-插件原理

来源:互联网 发布:淘宝东大门代购哪家好 编辑:程序博客网 时间:2024/06/11 21:04

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

代理链的生成

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

[java] view plaincopyprint?
  1. executor =(Executor) interceptorChain.pluginAll(executor);  

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

[java] view plaincopyprint?
  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 plaincopyprint?
  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 plaincopyprint?
  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 plaincopyprint?
  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 plaincopyprint?
  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 plaincopyprint?
  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方法,否者直接返回目标本省,这样可以减少目标被代理的次数。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 声卡驱动删除了怎么办 白色水彩没有了怎么办 大学毕业我想考军校怎么办? 考军校分数不够怎么办 大专工作不好找怎么办 小学二年级插班怎么办 进厂年龄不到怎么办 入伍批准书丢了怎么办 考驾照期间参军怎么办 学位房被占用怎么办 教室里回音太大怎么办 教室里味道太大怎么办 键盘只能打拼音怎么办 一师一优课件上传慢怎么办? 药店买药不给退怎么办 小孩热感冒发烧怎么办 孕妇热感冒了怎么办 孕妇热感冒喉咙痛怎么办 空军一号有划痕怎么办 高铁上乘客太吵怎么办 军官礼服丢了怎么办 空军大檐帽帽袋坏了怎么办 保安不发工资怎么办 做保安工资不资不发怎么办 公安改革辅警怎么办 皮带带子丢了怎么办 警校学生证丢了怎么办 警校证丢了怎么办 盘查没带身份证怎么办 网线拔不出来怎么办 车间压强差过大怎么办 不遵守交通规则交警怎么办 西裤屁股磨出光该怎么办 中暑发烧不退烧怎么办 小孩中暑反复发烧怎么办 上火导致的发烧怎么办 夏季运动中暑后怎么办 感觉中暑了头疼怎么办 轻度中暑怎么办小妙招 人中暑了头疼怎么办 中暑怎么办三字步骤