mybatisソースコード分析05:インターセプター

注:この一連のソースコード分析は、mybatis 3.5.6、ソースコードgitee倉庫倉庫アドレス:funcy/mybatisに基づいています。

mybatisのインターセプターはですorg.apache.ibatis.plugin.Interceptor。これは一部の操作をインターセプトできます。対応するmybatisドキュメントはmybatisプラグインです。

ドキュメントによると、最初にデモを準備しましょう!

1.インターセプターデモを準備します。実行されたSQLを出力します

ドキュメントの内容に応じて、インターセプターを用意します。

// 指定拦截的方法: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;
  }
}
复制代码

インターセプターをmybatis構成ファイルに追加します。

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

実行Test01すると、結果は次のようになります。

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

ご覧のとおり、実行されたSQLは正常に出力されます。

2.インターセプターアセンブリ

2.1分析

インターセプターはmybatis構成ファイルで構成されているため、XMLConfigBuilder#parseConfigurationインターセプターは解析構成ファイルでも解析されます。

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

解析操作は比較的一般的であるため、あまり分析しません。主にconfiguration.addInterceptor(...)メソッドを見て、インターセプターの最終的な宛先を確認しましょう。

public class Configuration {

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

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

  ...

}
复制代码

インターセプターは最終的にクラスConfigurationのメンバー変数に保存されます。interceptorChain

これは何InterceptorChainですか?私たちは続けます:

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

}
复制代码

コードの観点からは、List内部で型のメンバー変数を維持し、外部で操作メソッドを提供addXxx(...)getXxx()ます。

2.2組み立て

mybatisのインターセプターは、クラスConfigurationのメンバー変数interceptorChainに格納されますが、これらのインターセプターはどのようmybatisに実行チェーンに組み込まれますか?Configuration#newExecutor(...)次の行を持つメソッドに戻りましょう。

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

InterceptorChain#pluginAllこれは、インターセプターのロードを処理するために使用されます。

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

ここで渡される型targetExecutorであり、返される型もExecutorです。

引き続きinterceptor.plugin()メソッドを入力します。

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(...)方法,否则就直接调用该方法。

の実行ではinterceptor.intercept(...)、渡されるパラメータは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);
  }

}
复制代码

このクラスは主に保存目标对象目标方法、メソッドを传入目标方法的参数オーバーライドInterceptor#intercept(...)すると、これらの内容に基づいて一連の操作を完了することができます。

Invocation重要なメソッドもありますproceed()このメソッドは、ターゲットメソッドのロジックを実行します。実装しているSqlInterceptorので、これを使用します。

  @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()、元のロジックを実行するために呼び出すことを忘れないでください。

4.まとめ

この記事では、mybatisインターセプターメカニズムを分析し、解析、アセンブリ、および実行のプロセスを分析します。最終的な分析では、インターセプターは引き続きjdkによって提供される動的プロキシ機能を使用します。

mybatisインターセプターの関連分析はここにあります。


この記事の元のテキストへのリンク:my.oschina.net/funcy/blog/…、著者の個人的なレベルに限定されており、テキストには必然的に間違いがあります。私を訂正することを歓迎します!オリジナリティは簡単ではありません。商用の再版の場合は、著者に許可を求めてください。非商用の再版の場合は、出典を示してください。

おすすめ

転載: juejin.im/post/7102812108902891550