Mybatisフレームワークのソースコードメモ(8) Pluginプラグインの原理分析

1. プラグインの概要

公式サイトより翻訳引用

MyBatis を使用すると、マップされたステートメントの実行中の特定の時点で呼び出しをインターセプトできます。デフォルトでは、MyBatis はプラグインの使用を許可して、次のようなメソッド呼び出しをインターセプトします。

  • エグゼキューター (更新、クエリ、flushStatements、コミット、ロールバック、getTransaction、close、isClosed)
  • ParameterHandler (getParameterObject、setParameters)
  • ResultSetHandler (handleResultSets、handleOutputParameters)
  • StatementHandler (準備、パラメータ化、バッチ、更新、クエリ)

これらのクラスのメソッドの詳細は、各メソッドのシグネチャを確認するか、MyBatis ディストリビューションのソース コードを直接確認することで確認できます。
メソッドの呼び出しを監視する以上のことを行いたい場合は、オーバーライドするメソッドの動作をよく理解しておく必要があります。既存のメソッドの動作を変更またはオーバーライドしようとすると、MyBatis のコア モジュールが壊れる可能性が高いためです。これらは下位レベルのクラスおよびメソッドであるため、プラグインを使用する場合は注意してください。

2. カスタムプラグインの実装例

2.1 カスタム プラグインの実装手順

カスタム プラグインを実装する手順は大まかに次のとおりです。

  • MybatisフレームワークのInterceptorインターフェースを実装する

  • グローバル設定ファイル mybatis-config.xml でカスタム プラグインを設定するだけです

以下は、特定のプロセスを示すサンプル コードです。

2.2 カスタムプラグインのサンプルコード

ここでは、カスタマイズされた SQL ステートメントのインターセプト メソッドを示します。SQL ステートメントの実行が完了すると、返されたコレクション内の各出力オブジェクトの特定の属性が変更されます (これは、使用方法を示すためだけです。デモ コード)シナリオは汎用的ではないため使用できません。制作についてはお知らせください。興味があれば、自分で研究して開発することもできます)

カスタム インターセプターMyInterceptor
ここに画像の説明を挿入します

package com.kkarma.plugins;

import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Properties;


/**
 * @author kkarma
 * @date 2023/1/17
 */
@Intercepts({
    
    
        @Signature(type = Executor.class, method = "query",
                args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyInterceptor implements Interceptor {
    
    

    private String author;

    /**
     * 执行拦截逻辑的方法
     * @param invocation
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        System.out.println("method will be invoked......");
        List<LibBook> list = (List<LibBook>)invocation.proceed();
        list.stream().forEach(v -> v.setBookName(v.getBookName() + "----mod"));
        System.out.println("method has been invoked......");
        return list;
    }

    /**
     * 是否触发intercept方法
     * @param target
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    /**
     * 自定义插件属性参数配置
     * @param properties
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public void setProperties(Properties properties) {
    
    
        Object author = properties.get("author");
        System.out.println(author);
    }

    public String getAuthor() {
    
    
        return author;
    }

    public void setAuthor(String author) {
    
    
        this.author = author;
    }
}

グローバル設定ファイル mybatis-config.xml を変更します。

这里注意Mybatis-config.xml文件中各个标签元素的声明顺序
ここに画像の説明を挿入します

グローバル構成ファイルにカスタム プラグインを導入する
ここに画像の説明を挿入します

    <plugins>
        <plugin interceptor="com.kkarma.plugins.MyInterceptor">
            <property name="author" value="kkarma"/>
        </plugin>
    </plugins>

上記はカスタム プラグインの実装手順のすべてです。非常に簡単ですか? テストしてみましょう。

2.3 カスタム プラグインのサンプル コードのテスト

データベース内の 1 つのテーブル内のすべてのデータをクエリする単体テストを作成して、カスタム プラグインが有効かどうかを確認してみましょう。

@Test
public void testInterceptor() {
    
    
	SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
    try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
    
    
        SqlSessionFactory factory = factoryBuilder.build(ins);
        SqlSession sqlSession = factory.openSession(true);
        SqlSession sqlSession1 = factory.openSession(true);
        LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
        List<LibBook> libBooks = mapper.selectAllBook();
        libBooks.forEach(System.out::println);
        System.out.println("------------------------------------------");
        sqlSession.close();
    } catch (IOException e) {
    
    
        e.printStackTrace();
    }
}

ここに画像の説明を挿入します
ここに画像の説明を挿入します
これは、カスタム プラグインが有効になったことを示しています。ここから、プラグインをカスタマイズする手順は比較的簡単であることがわかります。次に、Mybatis のソース コードを通じてプラグインの実装原理を分析します。 。

3 プラグイン実装原理の分析

3.1 プラグインの解析と導入の仕組み

上記のコード例では、Mybatis-config.xml でカスタム インターセプターを宣言しました。その場合、カスタム プラグイン モジュールの解析と処理を担当するグローバル設定ファイルの解析クラスに専用のパーティが必要です。メソッド、
ここに画像の説明を挿入します
XMLConfigBuilder クラス の parseConfiguration() メソッドで呼び出されます。pluginEelment
ここに画像の説明を挿入します

pluginEelment メソッドは、グローバル構成ファイル内のプラグイン タグを解析し、対応する Interceptor オブジェクトを作成し、対応する属性情報をカプセル化するために使用されます。最後に、Configuration オブジェクトaddInterceptor(interceptorInstance) が呼び出され、次のようにインターセプタの登録ここに画像の説明を挿入します
configuration.addInterceptor(interceptorInstance)が完了しました。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
のマイバティスInterceptorChain
ここに画像の説明を挿入します

3.2 プラグインは誰を対象としていますか?

冒頭で説明しました。

MyBatis を使用すると、マップされたステートメントの実行中の特定の時点で呼び出しをインターセプトできます。デフォルトでは、MyBatis はプラグインの使用を許可して、次のようなメソッド呼び出しをインターセプトします。

  • エグゼキューター (更新、クエリ、flushStatements、コミット、ロールバック、getTransaction、close、isClosed)
  • ParameterHandler (getParameterObject、setParameters)
  • ResultSetHandler (handleResultSets、handleOutputParameters)
  • StatementHandler (準備、パラメータ化、バッチ、更新、クエリ)

ExecutorStatementHandler ParameterHandlerResultSetHandler オブジェクトについて説明する前に作成プロセス中に、インターセプターがこれらのオブジェクトをインターセプトして機能拡張を実装することについて簡単に説明しました。見てみましょう。上記の 4 つのコア クラスは、作成が失敗した場合、
Configuration クラス内の次のコードを呼び出します。

  • エグゼキュータオブジェクトの作成
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : 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;
  }

Executor.query() メソッドを呼び出すと、インターセプタが機能し始めます。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

  • StatementHandler オブジェクトの作成
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 这里我们的拦截器链会进行拦截
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

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

  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;
  }
  • ParameterHandler オブジェクトの作成
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    
    
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 这里我们的拦截器链会进行拦截
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  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;
  }
  • ResultSetHandlerオブジェクトの作成
  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;
  }

3.1 プラグインインターセプトの処理手順

3.1.1 単体プラグインのインターセプト処理プロセス

プロセス全体は責任連鎖モデルを使用して結合されます。

複数のインターセプターがインターセプター チェーンに保存され、すべてのインターセプターが走査され、Interceptor.plugin(Object target) メソッドが呼び出されます。

Interceptor.plugin(Object target) メソッドは、Plugin クラスの Wrap() メソッドを呼び出して、インターセプト ターゲット オブジェクトの動的プロキシ オブジェクトを返します。

Plugin クラスは InvocationHandler インターフェイスを実装しているため、最終的にプロキシ オブジェクトの invoke() メソッドが呼び出されます。

Plugin クラスの invoke() メソッドは、カスタム インターセプタのインターセプト (呼び出し呼び出し) メソッドを呼び出します。ここで、インターセプト処理ロジックは、プラグインの入出力を拡張するために実行する必要がある元のメソッドの前後に呼び出されます。方法。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

3.1.2 複数プラグインのインターセプト処理プロセス

複数のカスタム インターセプターがある場合、そのインターセプトの実行プロセスはどのようなものですか?

ここでは、 メソッドをインターセプトするインターセプター プラグインを追加しました。具体的なコード例を通じて推測を検証してみましょう。 Executor.classquery

package com.kkarma.plugins;

import com.kkarma.pojo.LibBook;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.List;
import java.util.Properties;


/**
 * @author kkarma
 * @date 2023/1/17
 */
@Intercepts({
    
    
        @Signature(type = Executor.class, method = "query",
                args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class MyLogInterceptor implements Interceptor {
    
    

    private String logger;

    /**
     * 执行拦截逻辑的方法
     * @param invocation
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        System.out.println("拦截方法执行之前记录日志...");
        List<LibBook> list = (List<LibBook>)invocation.proceed();
        System.out.println("拦截方法执行之后再次记录日志...");
        return list;
    }

    /**
     * 是否触发intercept方法
     * @param target
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }

    /**
     * 自定义插件属性参数配置
     * @param properties
     * @return
     * @throws Throwable
     * @author kkarma
     * @date 2023-01-17
     */
    @Override
    public void setProperties(Properties properties) {
    
    
        Object logger = properties.get("logger");
        System.out.println(logger);
    }

    public String getLogger() {
    
    
        return logger;
    }

    public void setLogger(String logger) {
    
    
        this.logger = logger;
    }
}

mybatis-config.xml にプラグインを登録する順序は次のとおりです。

    <plugins>
        <plugin interceptor="com.kkarma.plugins.MyInterceptor">
            <property name="author" value="kkarma"/>
        </plugin>
        <plugin interceptor="com.kkarma.plugins.MyLogInterceptor">
            <property name="logger" value="myLogger"/>
        </plugin>
    </plugins>

呼び出しを実行すると、コンソールに次のような結果が表示されます。
ここに画像の説明を挿入します

3.1.2.1 インターセプタの登録プロセス

まず、設定ファイルに登録する順序は、MyInterceptor -> MyLogInterceptor です。
ここに画像の説明を挿入します

3.1.2.2 プロキシオブジェクトの作成処理

設定ファイルから解析して作成した Interceptors オブジェクトは定義順に解析する必要があるため、ここで動的エージェントを作成する場合も登録順に作成されます。 MyInterceptor -> MyLogInterceptor
ここに画像の説明を挿入します

3.1.2.3 動的プロキシオブジェクトの呼び出し処理

ここに画像の説明を挿入します
実行を続けると、MyInteceptor の intercept() メソッドが実行され、次に query() メソッドが実行され、内側から外側に順番に戻ります。
ここに画像の説明を挿入します
印刷ログから、 执行调用的顺序注册和创建动态代理对象 の順序がまったく逆であることがわかります。
ここに画像の説明を挿入します

3.1.2.4 結論
  • 複数のプラグインを次の場所に登録しますInterceptorChain リストは解析され、構成ファイル内のプラグインによって定義された順序で上から下に追加されます。
  • プロキシ オブジェクトを作成する場合、InterceptorChain のリストの順序でもプロキシされます。
  • 呼び出しと実行のプロセスは、登録と作成のプロセスとまったく同じです相反

登録作成プロセス(ギフトラッピングプロセス):

ただ、私たちは今、他の子供たちに贈りたい美しい宇宙の砂(対象物)をたくさん持っているのですが、それを直接渡すことはできません。

そこであなたはボトル (MyInteceptor) を見つけて、宇宙砂を詰めました。

しかし、ボトルも醜いので、素敵なギフト ボックス (MyLogInteceptor) に梱包し、見た目が良くなり、友人にプレゼントします。

実行プロセス (ギフトを開けるプロセス) を呼び出します。

友人がギフトを受け取ると、まずギフト ボックスを開け (MyLogInteceptor.intercept() が呼び出されます)、ボトルの中で宇宙の砂が回転していることに気づきます。

そこで、再びボトルを開けて (MyInteceptor.intercept())、宇宙の砂 (対象物体) を注ぎ出すと、彼女は楽しく遊べるようになりました。

ここに画像の説明を挿入します
StatementHandler、ParameterHandler、ResultSetHandler のインターセプタは Executor と同じ処理フローになりますが、ここでは詳しく説明しませんので、興味があればご自身で勉強してください。

おすすめ

転載: blog.csdn.net/qq_41865652/article/details/129497582