análisis de código fuente de mybatis 05: interceptor

Nota: esta serie de análisis de código fuente se basa en mybatis 3.5.6, la dirección del almacén del almacén de gitee del código fuente: funcy/mybatis .

El interceptor de mybatis es org.apache.ibatis.plugin.Interceptor, que puede interceptar algunas operaciones, y el correspondiente documento de mybatis es mybatis plug-in

De acuerdo con la documentación, ¡primero preparemos una demostración!

1. Prepare la demostración del interceptor: imprima el sql ejecutado

De acuerdo con el contenido del documento, preparamos un interceptor:

// 指定拦截的方法:query 与 update
@Intercepts({
  @Signature(type= Executor.class, method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
  @Signature(type= Executor.class, method = "update",
    args = {MappedStatement.class, Object.class})
})
public class SqlInterceptor implements Interceptor {

  private Properties properties;

  /**
   * 处理拦截操作
   */
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    MappedStatement ms = (MappedStatement)args[0];
    BoundSql boundSql = ms.getBoundSql(args[1]);
    String sql = boundSql.getSql();
    System.out.println("执行的sql为:" + sql);
    return invocation.proceed();
  }

  /**
   * 设置一些属性
   */
  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}
复制代码

Agregue el interceptor al mybatisarchivo de configuración:

<configuration>
  <properties resource="org/apache/ibatis/demo/config.properties">
  </properties>
  <settings>
    ...
  </settings>
  <!-- 配置拦截器 -->
  <plugins>
    <plugin interceptor="org.apache.ibatis.demo.SqlInterceptor">
    </plugin>
  </plugins>
  <!-- 省略其他配置 -->
  ...
</configuration>
复制代码

Ejecutar Test01, los resultados son los siguientes:

执行的sql为:select id, login_name as loginName, nick from user
     where  id = ? 
     
      limit ?
[User{id=3, loginName='test', nick='HelloWorld'}]
复制代码

Como puede ver, el sql ejecutado se imprime con éxito.

2. Montaje del interceptor

2.1 Análisis

El interceptor se configura en el archivo de configuración de mybatis, por lo que el XMLConfigBuilder#parseConfigurationinterceptor también se analiza en el archivo de configuración de análisis:

  private void parseConfiguration(XNode root) {
    try {
      ...
      pluginElement(root.evalNode("plugins"));
      ...
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

  /**
   * 解析拦截器节点
   */
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取拦截器名称  
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        // 实际化
        Interceptor interceptorInstance 
            = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        // 添加拦截器
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
复制代码

La operación de análisis es relativamente convencional, por lo que no haré mucho análisis. Miremos principalmente el configuration.addInterceptor(...)método y veamos el destino final del interceptor:

public class Configuration {

  /**
   * 保存拦截器
   */
  protected final InterceptorChain interceptorChain = new InterceptorChain();

  /**
   * 将拦截器添加到 interceptorChain 中
   */
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

  ...

}
复制代码

Los interceptores finalmente se guardan en Configurationlas variables miembro interceptorChainde la clase.

¿Qué InterceptorChaines esto? Continuamos:

public class InterceptorChain {

  /**
   * 保存拦截器的list
   */
  private final List<Interceptor> interceptors = new ArrayList<>();

  ...

  /**
   * 添加拦截器
   */
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  /**
   * 获取所有的拦截器
   */
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}
复制代码

Desde el punto de vista del código, mantiene Listinternamente una variable miembro de un tipo y proporciona addXxx(...)métodos getXxx()de operación externamente.

2.2 Montaje

Los interceptores de mybatis se almacenan en Configurationlas variables miembro interceptorChainde la clase ¿Cómo se ensamblan estos interceptores a mybatisla cadena de ejecución? Volvamos al Configuration#newExecutor(...)método, que tiene esta línea:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ...
    // 处理 plugin(插件)
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
复制代码

InterceptorChain#pluginAllSe utiliza para manejar la carga de interceptores:

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // 调用各个拦截器的 plugin(...) 方法
      target = interceptor.plugin(target);
    }
    return target;
  }
复制代码

El tipo pasado aquí targetes Executor, y el tipo devuelto también es Executor.

Continúe para ingresar el interceptor.plugin()método:

public interface Interceptor {

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

  ...
}
复制代码

这是接口的默认方法,调用的是Plugin.wrap(...),我们继续:

public class Plugin implements InvocationHandler {

  /** 目标对象,如:executor */
  private final Object target;
  /** 拦截器 */
  private final Interceptor interceptor;
  /** 保存拦截的方法 */
  private final Map<Class<?>, Set<Method>> signatureMap;

  /**
   * 私有的构造方法,在 wrap(...) 方法中调用
   */
  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);
    if (interfaces.length > 0) {
      // 生成代理对象
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          // InvocationHandler 实例
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

    /**
   *  解析拦截器类上的标签
   * @param interceptor
   * @return
   */
  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(...);
    }
    // 处理 @Intercepts 中的 @Signature 注解
    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(...);
      }
    }
    return signatureMap;
  }

  ...
}
复制代码

从代码来看,Plugin.wrap(...)会使用jdk的动态代理功能生成代理对象,Plugin实现了InvocationHandler,它的invoke(...)方法是拦截的关键,我们后面再分析。

示例中,传入的是原始的executor,调用executor = (Executor) interceptorChain.pluginAll(executor)得到的executor就是动态代理类了:

本文使用的示例是拦截Executorqueryupdate方法,实际上还有其他方法可拦截,mybatis拦截器支持的方法如下:

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

这些方法的拦截配置同Executor相差不大,就不多作分析了。

3. 执行

在上一节的分析中,我们得到的是executor的动态代理对象,那么它是何时被执行的呢?

实际上,被动态代理的对象,执行方法时,都会调用其InvocationHandler实例的invoke(...)方法,executor的动态代理对象的InvocationHandlerPlugin,我们进入其invoke(...)方法:

  @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);
    }
  }
复制代码

Plugin#invoke(...)方法中,先会判断方法要不要拦截,对于需要拦截的方法,调用其interceptor.intercept(...)方法,否则就直接调用该方法。

En interceptor.intercept(...)la ejecución de , el parámetro que se pasa es Invocation, veamos cuál es:

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

}
复制代码

Esta clase principalmente guarda 目标对象y 目标方法, 传入目标方法的参数al anular Interceptor#intercept(...)métodos, podemos completar una serie de operaciones basadas en estos contenidos.

También Invocationhay un método importante: proceed()este método ejecutará la lógica del método de destino, ya que estamos implementando SqlInterceptor, esto es lo que usaremos:

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // 从 invocation 中获取参数
    Object[] args = invocation.getArgs();
    MappedStatement ms = (MappedStatement)args[0];
    BoundSql boundSql = ms.getBoundSql(args[1]);
    String sql = boundSql.getSql();
    System.out.println("执行的sql为:" + sql);
    // 处理完拦截逻辑后,要执行目标方法
    return invocation.proceed();
  }
复制代码

invocation.proceed()No olvide llamar para ejecutar la lógica original después de manejar la intercepción .

4. Resumen

Este artículo analiza el mybatismecanismo del interceptor y analiza el proceso de análisis, ensamblaje y ejecución.En el análisis final, el interceptor aún utiliza la función de proxy dinámico proporcionada por jdk.

El análisis relevante de mybatislos interceptores está aquí.


El enlace al texto original de este artículo: my.oschina.net/funcy/blog/… , limitado al nivel personal del autor, inevitablemente hay errores en el texto, ¡bienvenidos a corregirme! La originalidad no es fácil. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización. Para reimpresiones no comerciales, indique la fuente.

Supongo que te gusta

Origin juejin.im/post/7102812108902891550
Recomendado
Clasificación