`

Mybatis 插件原理

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

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}

方法签名接口.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * Returns the java type.
   *
   * @return the java type
   */
  Class<?> type();

  /**
   * Returns the method name.
   *
   * @return the method name
   */
  String method();

  /**
   * Returns java types for method argument.
   * @return java types for method argument
   */
  Class<?>[] args();
}

注入点.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  /**
   * Returns method signatures to intercept.
   *
   * @return method signatures
   */
  Signature[] value();
}


扩展 Plugin 必须实现该接口.
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

Invocation 可以理解为会话,也就是说其会封装调用的方法,参数等数据. 类似于 Dubbo 中的 Invocation 的概念.
public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

InterceptorChain 字面含义是 Interceptor 链. 就是将所有的 Interceptor 组成一条链,这个和 dubbo 中 filter 的设计有些类似.
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

我们看下 Plugin 类中比较重要的一个方法.
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 {
        // 如果自定义过插件的话,就知道这里其实就是获取的 Interceptor 注解里面定义的方法.
        // 换句话说该方法就是被拦截的方法,这时候通过代理,就可以达到我们的目的.
        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;
  }

看下核心方法.
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;
  }

代理执行的方法.
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);
    }
  }

看到这里,是不是有一个疑问?如果代理执行了,那怎么回到我们的 query 方法了?
答案 Interceptor 中.
invocation.proceed() 方法会让程序回到最终的方法上.
public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
0
2
分享到:
评论

相关推荐

    02-02-03-MyBatis插件原理及Spring集成1

    1、猜想 2、插件编写与注册 1、编写自己的插件类 3、插件登记 3、代理和拦截是怎么实现的 4、PageHelper 原理 1、用法(EmployeeCont

    深入浅出MyBatis技术原理与实战(高清带目录版)

    《深入浅出MyBatis技术原理与实战》分为3 个部分,依次介绍了MyBatis 的基础应用、原理及插件开发、实践应用,使读者能够由浅入深、循序渐进地掌握MyBatis 技术。首先,《深入浅出MyBatis技术原理与实战》在官方API ...

    《深入浅出MyBatis技术原理与实战》高清完整PDF下载

    接着介绍了MyBatis的运行原理和插件开发并配有一个完整的插件例子。为了增加实用性,作者还介绍了MyBatis-Spring项目,使得读者能够学习到如何把MyBatis整合到Spring项目中,最后作者还将讲解一些常用实例,比如Blob...

    深入浅出MyBatis技术原理与实战

    , 《深入浅出MyBatis技术原理与实战》分为3 个部分,依次介绍了MyBatis 的基础应用、原理及插件开发、实践应用,使读者能够由浅入深、循序渐进地掌握MyBatis 技术。首先,《深入浅出MyBatis技术原理与实战》在官方...

    深入浅出MyBatis技术原理与实战.pdf

     《深入浅出MyBatis技术原理与实战》分为3个部分,依次介绍了MyBatis的基础应用、原理及插件开发、实践应用,使读者能够由浅入深、循序渐进地掌握MyBatis技术。首先,《深入浅出MyBatis技术原理与实战》在官方API的...

    一图读懂mybatis插件plugin原理

    一图读懂mybatis插件plugin原理

    MyBatis自定义插件原理

    MyBatis自定义插件原理

    Mybatis常见知识点.md

    - Mybatis插件原理 - Mybatis一级缓存 - 一级缓存的原理 - 使得Mybatis一级缓存失效的方法 - Mybatis二级缓存 - Mybatis二级缓存的原理 - Mybatis缓存的缺点 &lt;!-- /TOC --&gt; Mybatis常见知识点 Mybatis优点...

    mybatis慢SQL插件

    基于mybatis的慢SQL小插件,原理是mybatis拦截器。只需要在springboot的配置文件做简单的配置,mybatis拦截器将SQL中所有参数自动做了填充。拦截器监控慢SQL并将完整的可执行的SQL语句打印在日志文件中,复制该SQL...

Global site tag (gtag.js) - Google Analytics