揭开Mybatis插件的神秘面纱

Mybatis插件的作用:
Mybatis的插件主要用于拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler这些组件的行为,在行为之前或者之后做拦截处理,进而改变行为执行细节,当然我们也可以完全重写这些行为。

好处:
说到好处,先剖析下插件的实现细节。插件实质是对JDK原生Proxy工具类的进一步封装,继承了JDK动态代理的优良基因,即可以对被代理对象的方法代码的无侵入式代理,不影响上游的接口调用,实现灵活可配置化。

注:当然Mybatis本身机制比较完善化,Mybatis官方源码本身并没有实现任何Interceptor,只是给出了一些使用样例,而用户可以参照样例去实现自己的Interceptor以满足多样化的需求

实现细节:
用于支持插件的相关类放在org.apache.ibatis.plugin 包中。
该包下面的一些主要类如下:

Interceptor
Plugin
Invocation
@Intercepts
@Signature
InterceptorChain

Interceptor是插件的接口,用户用于实现这个接口来定义自己的Interceptor,之所以用这个代表拦截意思的单词命名,Mybatis是考虑到插件本质是用于拦截方法。
在该接口中主要定义了如下两个方法:

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

我们通过实现interceptor接口,可以拿到invocation对应,invocation是Mybatis封装的方法切入点信息,我们可以拿到被代理的对象、拦截的方法、方法的参数;这样我们就可以在本来对象的方法调用之前及之后加入我们想要做的事,interceptor类也就是我们要做核心工作。
plugin则是用于生成代理对象的实现细节,Mybatis默认为我们提供了一个Plugin类,类似于工具类,这个Plugin类已经帮我们实现了生成代理对象的细节。
而我们只需要调用Plugin的方法如下:

  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;
  }

这个方法第一行代码调用getSignatureMap方法,我们再看这个方法:

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      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;
  }

这个方法很显然类似一个工具方法,传入我们实现的Interceptor对象,它会获取这个类对应的实现类上的@Intercepts注解信息,这个注解用于代码用户要拦截哪些方法或者说是哪些切入点,而每个切入点用注解@Signature 来代表,它用来描述用户要代理哪个接口的哪个方法,以及方法的参数列表,以及更好的确定唯一的一个方法签名,最终把用户要代理的类型与方法的映射关系构造出来。

然后回到上面的wrap方法,接下来的2,3行代码实质是把传进来的要代理的对象的所有父层接口类型与interceptor上注解中解析出来的类型进行比对,留下来代理的对象中有的类型。然后如果发现无可代理的类型则直接返回原来的对象,如果有则调用Proxy方法去生成代理对象,传入这些要代理的类型,然后我们发现还需要传入一个InvocationHandler对象。此时我们巧妙的发现,Plugin本身实现了这个InvocationHandler。所以很自然的new 一个Plugin,把要代理的对象及我们上面代表了实现的拦截核心业务interceptor对象传入作为Plugin的构造参数,最终返回一个代理后的对象。

运行时分析:
承接上面的大家可能会有疑惑:传入的是Plugin这个InvocationHandler对象,那应该调用Plugin重写的invoke方法,怎么会调用到interceptor的interceptor方法。这里就要明白上面实质我们已经把interceptor对应交给了plugin,plugin的invoke方法其实会把调用信息封装为invocation对象再回传给我们的intercepor方法,代码如下:

  @Override
  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);
    }
  }

看到这里明白了吗?如果获取不到要拦截的方法,或者说我们在配置拦截方法时忘记配置的话,这个方法会巧妙的丢弃我们写的inerceptor方法实现,转而调用对象本来的方法,这样这个额外的处理就封装在这个方法中做处理,而我们只需要关心拦截到方法时,去实现我们的核心拦截逻辑。

看到这里不得不佩服Mybatis Plugin的封装巧妙实现,它能够让我们实现在方法上细粒度的意向代理,这样可以让我们避免不小心污染了一个对象的所有实现方法,而且处理了很多细节,使我们在使用代理的时候非常简单。

总结:
Mybatis提供的Interceptor机制,大家可以理解为工具级别的,我们平常其它地方也可以用它来代理我们自己的对象,来满足我们实际开始中的一些代理需求。当然Mybatis内部插件机制只支持用它代理Executor、StatementHandler、ParameterHandler、ResultSetHandler

注:本文纯属本人手打真实剖析,不存在抄袭,请尊重开发人的劳动成果,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值