Mybatis插件的基本原理

2023年 7月 12日 44.1k 0

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, Set> 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 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, Set> signatureMap = new HashMap();
    //循环所有的标签
    for (Signature sig : sigs) {
      Set 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 c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class[0]);
  }

}

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论