MyBatis(5)-MyBatisのプラグインメカニズムを深く理解する


MyBatis(5)-MyBatisのプラグインメカニズムを深く理解する
MyBatisは、プラグインメカニズムをカスタマイズするための拡張を提供し、本質的にはブロッカーの機能を実現しています.
個人ホームページ:tuzhenyu’s page原文アドレス:MyBatis(五)—MyBatisのプラグインメカニズムを深く理解する
(0)カードメカニズム
  • MyBatisでは、プラグインを使用して特定のメソッド呼び出しをブロックできます.特定のブロックポイントには、次の4種類があります.
  • Executer:update()、query()、flushStatement()、commit()、rollback()、getTransaction()、close()、isClosed()などの方法;
  • ParameterHandler:getParameterObject()、setParameters()などの方法;
  • ResultSetHandler:handleResultSets(),handleOutputParameters();
  • StatementHandler:prepare(),parameterize(),batch(),update(),query()

  • コンフィギュレーションオブジェクトを初期化すると、解析ラベルがInterceptorインスタンスを生成し、ブロッキングチェーンInterceptorChainを追加します.プラグイン生成のタイミングはExecutor,ParameterHandler,ResultSet Handler,StatementHandlerの4つのインタフェースの実装クラス初期化の際にpluginAll()メソッドが呼び出され,InterceptorChainブロッキングチェーンのブロッキングに基づいてエージェントクラスを生成するか否かが決定される.
  • ブロッキングが実行される順序は、Executor、ParameterHandler、ResultSet Handler、StatamentHandlerである.

  • (1)カードの使用
  • プラグインクラスExamplePlugin実装インタフェースInterceptorを作成し、主なプラグインロジックはinterceptor()メソッドで実装される.
  • @Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
    public class ExamplePlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                return invocation.proceed();
            }finally {
                long end = System.currentTimeMillis();
                System.out.println("cost time:"+(end-start));
            }
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target,this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
  • はconfig.xmlプロファイルにplugins構成を追加します.ノードは順序付きです.
  • properties/settings/typeAliases/typeHandlers/objectFactory/objectWrapperFactory/plugins/environments/databaseIdProvider/mappersなのでplugins構成はenvironmentとmappersの前に必要です.
    <plugins>
        <plugin interceptor="mybatis.interceptor.ExamplePlugin">plugin>
    plugins>

    (2)プラグインメカニズムの実現プロセス
    1.プラグイン構成の初期化
  • コンフィギュレーションオブジェクトが初期化されると、pluginElement()メソッドを呼び出してラベルを解析します.
  • タグ対応intercptorクラス名を取得し、反射により対応するブロッカークラスインスタンスinterceptorを生成する.
  • ラベルの下のすべてのラベルを取得し、Propertiesオブジェクトに解析し、Propertiesをブロッカーに設定します.
  • 生成されたブロッキングインスタンスinterceptorをconfigurationに入れる.

  • 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).newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
          }
        }
    }
  • addInterceptor()メソッドを呼び出し、configurationの属性InterceptorChainにInterceptorインスタンスを入れる.InterceptorChainはInterceptorのチェーンテーブルです.
  • public void addInterceptor(Interceptor interceptor) {
      interceptorChain.addInterceptor(interceptor);
    }
    public class InterceptorChain {
    
      private final List 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 getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }

    2.ブロッキングの実行フロー
    Executorのブロッカーは、sqlSession生成フェーズでExecutorインスタンスを作成するときにpluginAll()メソッドでブロッカーを追加します.
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
      executorType = executorType == null ? defaultExecutorType : executorType;
      executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
      Executor executor;
      if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
      } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
      } else {
        executor = new SimpleExecutor(this, transaction);
      }
      if (cacheEnabled) {
        executor = new CachingExecutor(executor);
      }
      executor = (Executor) interceptorChain.pluginAll(executor);
      return executor;
    }

    ParameterHandler,ResultSet Handler,StatamentHandlerブロッキングはSQLの特定の実行フェーズでpluginAll()メソッドでブロッキングを追加します.
  • StatementHandler初期化時にpluginAll()メソッドを呼び出してブロッキング
  • を追加する.
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
    }
  • StatementHandler初期化時にParameterHandlerとResultSetHandlerインスタンス
  • が作成されます.
    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      this.configuration = mappedStatement.getConfiguration();
      this.executor = executor;
      this.mappedStatement = mappedStatement;
      this.rowBounds = rowBounds;
    
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      this.objectFactory = configuration.getObjectFactory();
    
      if (boundSql == null) { // issue #435, get the key before calculating the statement
        generateKeys(parameterObject);
        boundSql = mappedStatement.getBoundSql(parameterObject);
      }
    
      this.boundSql = boundSql;
    
      this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
      this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    }
  • ParameterHandler初期化時にpluginAll()メソッドを呼び出してブロッキング
  • を追加
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
      ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
      parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
      return parameterHandler;
    }
  • ResultSetHandler初期化時にpluginAll()メソッドを呼び出してブロッキングを追加
  • public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
        ResultHandler resultHandler, BoundSql boundSql) {
      ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
      resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
      return resultSetHandler;
    }

    ブロッキングの追加
  • プラグイン生成のタイミングはpluginAll()メソッドを呼び出すときであり、ここで形参targetはExecutor、ParameterHandler、ResultSet Handler、StatamentHandlerインタフェースの実装クラスであり、ブロックチェーンInterceptorChainのすべてのブロックを巡り、一致すればtargetのエージェントクラスを作成する.ブロックチェーン内の複数のブロックで生成されるエージェントクラスはネスト構造であり、すなわち、前のブロックで生成されたエージェントクラスに対してエージェント生成エージェントクラスのエージェントクラスを再実行する.
  • は、ブロッカー内のplugin()メソッドを呼び出してマッチングし、マッチングすると対応するエージェントクラスを生成する.
  • public Object pluginAll(Object target) {
      for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
      }
      return target;
    }
  • plugin()メソッドは主にExecutor,ParameterHandler,ResultSet Handler,StatamentHandlerの実装クラス生成エージェントであり,これらのクラスのメソッドを呼び出すときにInvocationHandlerのinvoke()メソッドを直接呼び出す.
  • @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }
  • pluginメソッドでMyBatisが提供する既存の生成エージェントを呼び出すメソッドPlugin.wrap()メソッド
  • は、ブロックの要求に適合するすべての方法署名
  • を取得する.
    public static Object wrap(Object target, Interceptor interceptor) {
      Map, Set> 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;
    }
  • getSignatureMap()メソッド取得メソッド署名を呼び出し、すべてのSignatureラベルコンテンツを巡り、type(Executor.class)の下でmethodを満たすすべてのメソッドを取得し、メソッド署名マッピングを構築する.
  • メソッド署名マッピングは、Map
  • である.
    {interface org.apache.ibatis.executor.statement.StatementHandler=[public abstract int org.apache.ibatis.executor.statement.StatementHandler.update(java.sql.
    Statement) throws java.sql.SQLException, public abstract java.util.List org.apache.ibatis.executor.statement.StatementHandler.query(java.sql.Statement,org.apache.
    ibatis.session.ResultHandler) throws java.sql.SQLException]}
    private static Map<Class>, Set<Method>> getSignatureMap(Interceptor interceptor) {
      Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
      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<Class>, Set<Method>>();
      for (Signature sig : sigs) {
        Set methods = signatureMap.get(sig.type());
        if (methods == null) {
          methods = new HashSet();
          signatureMap.put(sig.type(), methods);
        }
        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);
        }
      }
      return signatureMap;
    }
  • SignatureMapメソッドラベルマッピングを取得した後、type(Executor.class)を介してtargetのすべてのインタフェースを取得し、メソッド署名にこのインタフェースがあればinterfacesリストに追加する.
  • private static Class>[] getAllInterfaces(Class> type, Map<Class>, Set<Method>> signatureMap) {
      Set> 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>[interfaces.size()]);
    }
  • 現在入力されているTargetのインタフェースに@Interceptors注釈で定義されたインタフェースがある場合、生成エージェントは、そうでなければ元のtargetに戻る.

  • エージェントの実装
  • エージェント実装後のExecutorなどは,そのメソッドを呼び出すとPluginのinvoke()メソッドを先に呼び出し,そのメソッドがエージェントされる必要があるかどうかを問い合わせる.言い換えれば,Executor,ParameterHandler,ResultSet Handler,StatementHandlerであり,@Intercepts注記ではどのメソッドの署名をブロックするかを定義する.
  • 現在呼び出されているメソッドのメソッド署名がメソッド署名セットにおいてエージェントが必要であることを示す場合、ブロッキングのintercept()メソッドが呼び出される.そうでなければ、エージェントを必要とせずに元の方法を実行します.
  • @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);
      }
    }
  • ブロッカーの論理実装インターセプト()メソッドにおける
  • @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        }finally {
            long end = System.currentTimeMillis();
            System.out.println("cost time:"+(end-start));
        }
    }

    (3)まとめ
  • プラグインメカニズムの実現は主に以下のステップに分けられる.
  • コンフィギュレーション初期化中にPluginsタグを解析してブロックチェーンInterceptorChain
  • に加える.
  • SQLの実行中にExecutor、ParameterHandlerが初期化されたときpluginAll()メソッドを呼び出して対応するエージェントクラスを生成します.
  • Executor、ParameterHandlerなどのクラスのメソッドを実行する場合、対応するエージェントクラスのメソッドが実行されます.

  • pluginAll()メソッドを呼び出してエージェントクラスを生成すると、ブロックチェーンリスト内のすべてのブロックを遍歴し、レイヤネストしてエージェントクラスを生成します.プロファイル内の下のプラグイン構成ほど、ネストされたエージェントクラスの外側にあります.
  • 階層のエージェントはシステムのパフォーマンスに影響を与えるため、不要なエージェントを減らす必要があります.