Mybatis:深入浅出-插件原理

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

代理链的生成

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

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

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

/** 
  * 每一个拦截器对目标类都进行一次代理 
  * @paramtarget 
  * @return 层层代理后的对象 
  */  
public ObjectpluginAll(Object target) {  
    for(Interceptor interceptor : interceptors) {  
        target= interceptor.plugin(target);  
    }  
    returntarget;  
}  

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

@Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})  
public class ExamplePlugin implements Interceptor {  
     @Override  
     public Objectintercept(Invocation invocation) throws Throwable {  
         returninvocation.proceed();  
     }  
   
     @Override  
     public Objectplugin(Object target) {  
         returnPlugin.wrap(target, this);  
     }  
   
     @Override  
     public voidsetProperties(Properties properties) {  
     }  
}  

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

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方法来实现的。代码如下:

public staticObject 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) {  
         //生成代理对象, Plugin对象为该代理对象的InvocationHandler  (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念)  
         returnProxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));  
     }  
     returntarget;  
}   

 这个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方法是怎么样的呢?

public Objectinvoke(Object proxy, Method method, Object[] args) throws Throwable {  
    try {  
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());  
        if(methods != null && methods.contains(method)) {  
             //调用代理类所属拦截器的intercept方法,  
            return interceptor.intercept(new Invocation(target, method, args));  
        }  
         returnmethod.invoke(target, args);  
    } catch(Exception e) {  
        throwExceptionUtil.unwrapThrowable(e);  
    }  
}

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

public class Invocation {  
  
  private Object target;  
  private Methodmethod;  
  private Object[]args;  
    
  public Invocation(Object target, Method method, Object[] args) {  
       this.target =target;  
       this.method =method;  
       this.args =args;  
   }  
   ...  
  
  public Objectproceed() throws InvocationTargetException, IllegalAccessException {  
    returnmethod.invoke(target, args);  
  }  
}  

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

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

public Object intercept(Invocation invocation) throws Throwable {  
 
     //完成代理类本身的逻辑  
     ...  
     //通过invocation.proceed()方法完成调用链的推进  
     return invocation.proceed();  
}   

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

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值