美文网首页
MyBatis印象阅读之插件解析

MyBatis印象阅读之插件解析

作者: 向光奔跑_ | 来源:发表于2019-08-19 10:52 被阅读0次

今天我们来讲下关于MyBatis的插件功能。为什么会有这个功能呢? 我们结合我们可能接触过的知识点类比可能能更好的理解。

在我看来,MyBatis的插件功能可以和Spring的AOP进行比较,主要功能都是为了扩展,当然功能没有AOP那么强大,也不需要。它们的相同点是可以在指定位置来进行业务功能的增强实现,不同点是AOP更加强大丰富。

比较完了,我们直接来进入实例去了解:

1. MyBatis插件实例

这里我还是以一个例子来抛砖引玉,我们进入到Test中的一个测试方法:

  @Test
  void mapPluginShouldInterceptGet() {
    Map map = new HashMap();
    map = (Map) new AlwaysMapPlugin().plugin(map);
    assertEquals("Always", map.get("Anything"));
  }
  
  
    @Intercepts({
      @Signature(type = Map.class, method = "get", args = {Object.class})})
  public static class AlwaysMapPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) {
      return "Always";
    }

    @Override
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
  }

这里有很多我们目前还没接触过的,我们一个个来分析。

1.1 接口Interceptor解析

public interface Interceptor {

  /**
   * 执行插件内容
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 封装
   */
  Object plugin(Object target);

  /**
   * 参数赋值
   */
  void setProperties(Properties properties);

}

大致熟悉有这样三个方法之后,我们来继续看他使用的注解有 @Intercepts、 @Signature具体功能我们根据之前的test方法和这个类来进行猜想一下,之后再来做分析,下面我们继续来看我们这唯一调用了一个MyBatis方法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;
  }

我们重点来看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<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      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;
  }

这里就可以看出我们之前 @Intercepts、 @Signature的作用,主要功能就是获取注解的信息,解析出来我们需要代理插件对应的方法,代码逻辑也比较清晰.下面一步是获取对应的接口,我们来看
getAllInterfaces:

  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    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()]);
  }

这里的代码逻辑都比较简单,获取我们class类对应的所有方法,包括他的父类,然后是否匹配我们注解对应的方法,有就添加。最后如果得到的接口不为空,则进行包装代理,代理方式是java的代理,这个我们不做展开,只要知道我们的Plugin实现了InvocationHandler接口:

public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
  
    @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);
    }
  }
}

整体的流程就是这样,结合进MyBatis,无非是代理的方法更换。不过重点要记住的是,MyBatis只支持在某几个点来进行代理,我们来看下官网:

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

2. 今日总结

我们今天来了解了关于MyBatis插件的源码,相对也比较简单。到这里,我们整体的MyBatis内容源码就讲的差不多了,收货还是有的,所以我要继续坚持下去~~~~

相关文章

网友评论

      本文标题:MyBatis印象阅读之插件解析

      本文链接:https://www.haomeiwen.com/subject/lhgjsctx.html