SpringとMyBatisの連携機構:@MapperScanから動的プロキシまでの実行フロー解析

Spring Context初期化におけるMyBatis統合の核心処理

Spring FrameworkがMyBatisと連携する際、その中心的な役割を果たすのが@MapperScanアノテーションです。本稿では、該当作付きアノテーションを契機としたBean定義の展開プロセス、インタフェースのプロキシ化ロジック、および関連する拡張ポイントの動作機制について詳解します。

1. @MapperScanとImportメカニズムの起動

@MapperScan内部では@Importを用いてカスタム登録クラスを読み込みます。定義は以下のように構成されます:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CustomMapperRegistrar.class)
public @interface MapperScan {
    String[] value() default {};
    // ...その他の属性省略
}

Springの起動時、ConfigurationClassPostProcessorが設定クラスを解析する段階で、このCustomMapperRegistrarregisterBeanDefinitionsメソッドが呼び出されます。ここで重要なのは、MyBatis側の設定クラスを直接コンテナに登録せず、後続の処理パイプラインを実行するためのトリガーとして機能させる点です。

2. BeanDefinitionRegistryPostProcessorによるスキャン処理の実行

インポートされた登録クラスは内部でCustomMapperConfigurer型のBeanDefinitionを作成し、登録します。このクラスはBeanDefinitionRegistryPostProcessorインターフェースを実装しているため、SpringのApplicationContext初期化フローにおいて後処理ステージで自動的にpostProcessBeanDefinitionRegistry()が実行されます。

このメソッド内では指定されたパッケージパスを対象にスキャナを初期化し、インタフェースを検出します。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.scan(basePackages[0]);
}

スキャンされた各インタフェースに対し、以下のプロパティを持つBeanDefinitionが構築されます:

  • ターゲットクラス:MappedBeanFactory.class
  • 注入モード:AUTOWIRE_BY_TYPE
  • カスタムプロパティ:mapperInterface(対象インタフェースのクラスオブジェクト)

3. FactoryBean経由のプロキシ生成と依存性解決

通常のBean取得プロセスにおいて、MappedBeanFactoryはSpringのFactoryBeanインターフェースを実装しています。そのため、context.getBean("targetDao")と呼び出した場合、返されるのはファクトリ自体ではなく、getObject()メソッドが返すインスタンスになります。

public class MappedBeanFactory<T> implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private SqlSessionManager sqlSessionMgr;

    // getter/setter省略

    @Override
    public T getObject() {
        return sqlSessionMgr.getMapper(mapperInterface);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

getMapper()内部ではMyBatisの設定レジストリから該当する実装クラスを取得し、JDKのProxy.newProxyInstance()を用いて実際のインタフェースをラップしたプロキシオブジェクトを生成します。プロキシの呼出ハンドラにはDaoInvocationHandlerが割り当てられます。

4. メソッド実行時の委譲チェーンとトランザクション管理

アプリケーション側でdao.selectById("id123")を呼び出すと、まずプロキシハンドラのinvoke()メソッドが実行されます。ここではメタデータを解析し、SQLコマンドタイプ(SELECT/INSERT/UPDATEなど)に応じたDaoCommandExecutorへ処理を委譲します。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    DaoCommandExecutor executor = resolveCommandExecutor(method);
    return executor.execute(sqlSessionTemplate, args);
}

特筆すべきは、SqlSessionTemplate自体も内部的に動的プロキシを使用している点です。SqlSessionInterceptorInvocationHandlerを実装しており、SQL実行前後のセッションオープン・コミット・クローズ処理、ならびに例外変換を一元管理します。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) {
        SqlSession session = openSessionFromDataSource();
        try {
            Object result = method.invoke(session, args);
            commitIfNecessary(session);
            return result;
        } finally {
            closeSessionSafely(session);
        }
    }
}

これにより、開発者は明示的なトランザクション境界制御を行わずとも、宣言的またはアノテーション駆動のDB操作が可能になります。なお、FactoryBeanを取得する際に接頭辞&を付与すると、ラップされたインスタンスではなくファクトリクラス自体の参照が得られる点も設計上の特徴です。

5. 拡張ポイントと自動設定の仕組み

標準的なスプリング設定に加え、独自ファクトリクラスを定義してBean定義をプログラム的に登録することも可能です。BeanDefinitionRegistryPostProcessorを実装したクラスを用意することで、コンテナ起動直後に任意のGenericBeanDefinitionを登録・修正できます。これは既存のアノテーションスキャンを上書きしたり、条件付きでBeanを追加したい場合に有効なパターンです。

さらに、Spring Boot環境ではMETA-INF/spring.factoriesファイルにEnableAutoConfigurationマッピングを追記することで、クラスパス上の特定の構成クラスを自動的に適用させることができます。これにより外部ライブラリやフレームワークの自動統合が行われ、設定クラスの手動記述量を大幅に削減します。

タグ: Spring Framework MyBatis Bean Definition Dynamic Proxy FactoryBean

6月7日 16:08 投稿