Mybatis插件的基本原理

MyBatis 提供了一种插件 (plugin) 的功能,虽然叫做插件,但其实这是拦截器功能。

我们下文中统一称为拦截器

. 拦截器的作用

MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些执行过程中的关键属性,从而影响SQL的生成、执行和返回结果。

. 拦截器的目标

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截 Executor 接口的部分方法,比如 update,query,commit,rollback 等方法,还有其他接口的一些方法等。

总体概括为:

拦截执行器的方法

拦截参数的处理

拦截结果集的处理

拦截 Sql 语法构建的处理

三.插件是如何起作用的

1.使用代理对象取代拦截目标

在Configuration类中,会创建出Executor,ResultSetHandler,ParameterHandler,StatementHandler的代理对象。

2.代理对象是如何生成的

代理对象在生成的过程中,主要是利用了责任链和动态代理两种设计模式。

拦截器责任链:

上图为多个拦截器循环,对目标对象,进行拦截,每拦截一次生成一个代理对象,最后生成一个最终的代理对象。

Mybatis使用的是jdk的动态代理,

jdk的动态代理的核心要点:

1.要实现InvocationHandler接口

2.使用Proxy.newProxyInstance()方法创建代理对象

我们先看下是如何创建代理对象和实现invoke方法的。

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

  /**
   *创建代理对象
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //获取该拦截器需要拦截的类和方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //获取拦截的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //接口数大于0
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

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

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //获取拦截器上的Intercepts注解
    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);
      }
    }
    //返回需要拦截的类和类中方法的Map
    return signatureMap;
  }

  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<?>[0]);
  }

}

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYXD0OFF' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片