インタビュアーが聞くのが大好きなSpringソースコード:SpringとMybatisの高度な統合

MybatisのSpring統合の原理を紹介する前に、まずMybatisの動作原理を紹介する必要があります。

Mybatisの基本的な動作原理

Mybatisでは、インターフェースを使用して実行するSQLを定義できます。簡略化したコードは次のとおりです:
インターフェースを定義し、@ Select はクエリSQLステートメントを実行することを意味します。

public interface UserMapper {
  @Select("select * from user where id = #{id}")
  User selectById(Integer id);
}

次に、実行SQLコードを示します。

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

// 以下使我们需要关注的重点
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
User user = mapper.selectById(id);

Mybatisの目的は、プログラマーがメソッドを呼び出して指定されたSQLを実行できるようにし、SQLを実行する基本的なロジックをカプセル化することです。

ここでは、次のマッパーオブジェクトに注目します。SqlSessionのgetMapperメソッドが呼び出されると、プロキシオブジェクトが受信インターフェイスに対して生成され、プロキシオブジェクトは実際にプログラムによって使用されます。プロキシオブジェクトのメソッドが呼び出されると、Mybatisはそれを取り出しますこのメソッドに対応するSQLステートメントは、JDBCを使用してSQLステートメントを実行するために使用され、最終的に結果が取得されます。

解決すべき問題を分析する

SpringとMybatisでは、このプロキシオブジェクトに注目する必要があります。統合の目的は、MapperのプロキシオブジェクトをBeanとしてSpringコンテナに配置することです。これにより、プロキシオブジェクトを通常のBeanのように使用できるようになります。たとえば、@ Autowireによってプロキシオブジェクトを自動的に挿入できます。

たとえば、SpringとMybatisが統合されている場合、次のコードを使用してMybatisでプロキシオブジェクトを使用できます。

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

UserServiceのuserMapper属性は、Mybatisのプロキシオブジェクトとして自動的に挿入されます。統合プロジェクトに基づいてデバッグする場合、userMapperのタイプはorg.apache.ibatis.binding.MapperProxy@41a0aa7dであることがわかります。証明は確かにMybatisのプロキシオブジェクトです。

では、ここで解決したい問題は、MybatisのプロキシオブジェクトをBeanとしてSpringコンテナに配置する方法ですか。

これを解決するには、SpringのBean生成プロセスを理解する必要があります。

春の豆生産工程

Springの起動プロセス中に、Beanはおおよそ次の手順で生成されます

  1. 指定されたパッケージパスのクラスファイルをスキャンします
  2. クラス情報に従って対応するBeanDefinitionを生成します
  3. ここでは、プログラマーは特定のメカニズムを使用してBeanDefinitionを変更できます。
  4. BeanDefinitionに従ってBeanインスタンスを生成します
  5. 生成されたBeanインスタンスをSpringコンテナーに配置する

クラスAがあるとすると、次のコードを想定します。

カテゴリーA:

@Component
public class A {
}

クラスB、@ Componentアノテーションはありません

public class B {
}

次のコードを実行します。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

出力結果は次のとおりです。com.luban.util.A@ 6acdbdf5

クラスAに対応するBeanオブジェクトタイプは、引き続きクラスAです。しかし、この結論は不確かです。BeanFactoryポストプロセッサを使用してBeanDefinitionを変更できます。BeanFactoryポストプロセッサを追加します。

@Component
public class LubanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
        beanDefinition.setBeanClassName(B.class.getName());
    }
}

これにより、元のクラスAに対応するBeanDefinitonが変更され、クラスBに変更されます。そのため、通常生成されるBeanオブジェクトのタイプはクラスBになります。現時点では、次のコードを呼び出すとエラーが報告されます。

context.getBean(A.class);

ただし、Bクラスに@Componentアノテーションはありませんが、次のコードを呼び出してもエラーは報告されません。

context.getBean(B.class);

また、次のコードによって返される結果は次のとおりです。com.luban.util.B@ 4b1c1ea0

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));

この問題について説明する理由は、問題を説明するためです。Springでは、Beanオブジェクトはクラスに直接関連していませんが、BeanDefinitionに直接関連しています。

では、解決したい問題に戻りましょう。MybatisのプロキシオブジェクトをBeanとしてSpringコンテナに入れるにはどうすればよいでしょうか。

Springでは、Beanを生成する場合は、最初にBeanDefinitionを生成する必要があります。これは、新しいオブジェクトインスタンスが必要であるのと同じように、最初にクラスが必要です。

問題を解く

質問に戻り、自分でBeanを生成したいので、BeanDefinitionにBeanオブジェクトのタイプを設定し、次にBeanDefinitionをSpringに追加することによって、BeanDefinitionがある限り、最初にBeanDefinitionを生成する必要があります。Springは自動的にBeanDefinitionに従いますタイプに対応するBeanオブジェクトの生成にご協力ください。

したがって、次の2つの問題を解決する必要があります。

  1. Mybatisプロキシオブジェクトのタイプは何ですか?BeanDefinitionに設定したいので
  2. SpringコンテナにBeanDefinitionをどのように追加しますか?

注:上記で使用したBeanFactoryポストプロセッサはBeanDefinitionを変更することしかできず、BeanDefinitionを追加することはできません。インポート技術を使用してBeanDefinitionを追加する必要があります。後で、インポート技術を使用してBeanDefinitionを追加する場合は、疑似コード実装のアイデアを見ることができます。

前提:UserMapperインターフェースがあり、彼のプロキシオブジェクトタイプはUserMapperProxyです。
だから私たちのアイデアはこのようなもので、疑似コードは次のとおりです:

BeanDefinitoin bd = new BeanDefinitoin();
bd.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(bd);

ただし、ここで深刻な問題があります。つまり、前述のUserMapperProxyは私たちの仮定です。これはプロキシクラスのタイプを表します。ただし、Mybatisのプロキシオブジェクトは、プロキシオブジェクトのプロキシであるJDKの動的プロキシテクノロジーを使用して実装されます。クラスは動的に生成され、プロキシオブジェクトのプロキシクラスが何であるかがわかりません。

それでは、質問に戻ります。Mybatisプロキシオブジェクトのタイプは何ですか。

2つの答えがあった可能性があります。

  1. プロキシオブジェクトに対応するプロキシクラス
  2. プロキシオブジェクトに対応するインターフェース

次に、プロキシクラスが動的に生成されるため、答え1はnoと同等です。次に、答え2を調べます。プロキシオブジェクトに対応するインターフェイス
答え2を採用した場合、私たちの考えは次のとおりです。

BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);

ただし、SpringにはこのBeanDefinitionに基づいて対応するタイプのインスタンスを作成する方法がなく、インターフェースがインスタンスを直接作成できないため、実際にはBeanDefinitionに対応するタイプをインターフェースとして設定しても機能しません。

だから今問題はここにあります、私が解決したい問題:Mybatisプロキシオブジェクトのタイプは何ですか?

どちらの回答も拒否されたため、この質問は解決できないため、この考え方に沿って考えることはできなくなり、元の質問に戻ることができます:MybatisのプロキシオブジェクトをBeanとして配置する方法春のコンテナで?

上記の推論を要約すると、BeanDefinitionのクラスタイプを設定すると、対応するBeanの生成がSpringによって自動的に支援されますが、この方法は現実的ではありません。

究極のソリューション

豆を生成する方法は他にありますか?そして、Beanを生成するロジックは、Springでは実行できません。自分で実行する必要があります。

FactoryBean

はい、それは春のFactoryBeanです。FactoryBeanを使用して、次のように、生成するBeanオブジェクトをカスタマイズできます。

@Component
public class LubanFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else {
                    // 执行代理逻辑
                    return null;
                }
            }
        });

        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserMapper.class;
    }
}

FactoryBeanを実装するLubanFactoryBeanを定義し、getObjectメソッドを使用して、Beanオブジェクトを生成するロジックをカスタマイズします。

次のコードを実行します。

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
        System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
        System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
    }
}

印刷されます:

lubanFactoryBean: com.luban.util.LubanFactoryBean$1@4d41cee
&lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94
lubanFactoryBean-class: class com.sun.proxy.$Proxy20

結果から、Springコンテナの「lubanFactoryBean」という名前のBeanオブジェクトが、カスタムjdk動的プロキシによって生成されたプロキシオブジェクトであることがわかります。

したがって、FactoryBeanを介してSpringコンテナにカスタムBeanオブジェクトを追加できます。上記で定義されたLubanFactoryBeanはUserMapperに対応します。つまり、UserMapperに対応するプロキシオブジェクトをBeanとしてコンテナに配置することと同等のLubanFactoryBeanを定義しました。

しかし、プログラマーとして、マッパーを定義するたびにLubanFactoryBeanを定義することは不可能です。これは非常に面倒です。LubanFactoryBeanを変換して、以下のような用途を広げましょう。

@Component
public class LubanFactoryBean implements FactoryBean {

    // 注意这里
    private Class mapperInterface;
    public LubanFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } else {
                    // 执行代理逻辑
                    return null;
                }
            }
        });

        return proxyInstance;
    }

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

LubanFactoryBeanを変換すると、LubanFactoryBeanが柔軟になり、LubanFactoryBeanが構築されると、構築を通じてさまざまなMapperインターフェースを渡すことができます。

実際、LubanFactoryBeanはBeanでもあり、BeanDefinitionを生成してLubanFactoryBeanを生成し、構築メソッドのパラメーターに異なる値を設定することもできます。たとえば、擬似コードは次のとおりです。

BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是LubanFactoryBean
bd.setBeanClassName(LubanFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用LubanFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);

特に2つ注意してください。つまり、現在のBeanDefinitionがBeanオブジェクトを生成するときに、LubanFactoryBeanの構築メソッドを呼び出してUserMapperのClassオブジェクトを渡すことによって生成されます。次にLubanFactoryBeanが生成されると、UserMapperインターフェースに対応するプロキシオブジェクトがBeanとして生成されます。

これまでのところ、解決したい問題は実際に完了しています。MybatisのプロキシオブジェクトをBeanとしてSpringコンテナーに配置します。ここでは、単純なJDKプロキシオブジェクトを使用してMybatisのプロキシオブジェクトをシミュレートしているだけです。時間があれば、Mybatisで提供されているメソッド領域を呼び出して、プロキシオブジェクトを生成できます。ここでは紹介しません。

インポート

この時点では、まだ行っていないことが1つあります。つまり、BeanDefinitionを実際に定義してSpringに追加する方法です。前述のように、インポートテクノロジーを使用する必要があります。たとえば、次のようにできます。

次のクラスを定義します。

public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.setBeanClass(LubanFactoryBean.class);
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        // 添加beanDefinition
        registry.registerBeanDefinition("luban"+UserMapper.class.getSimpleName(), beanDefinition);
    }
}

そして、AppConfigに@Importアノテーションを追加します。

@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {

このように、Springの起動時にBeanDefinitionが追加されます。BeanDefinitionはLubanFactoryBeanオブジェクトを生成し、LubanFactoryBeanオブジェクトを生成するときにUserMapper.classオブジェクトを渡します。LubanFactoryBean内のロジックを介して、UserMapperインターフェースのプロキシを自動的に生成することと同じですオブジェクトはBeanとして機能します。

総括する

要約すると、分析を通じて、SpringとMybatisを統合する必要があります。

  1. LubanFactoryBeanを定義する
  2. LubanImportBeanDefinitionRegistrarを定義する
  3. AppConfigにアノテーション@Import(LubanImportBeanDefinitionRegistrar.class)を追加します。

最適化

このようにして、統合要件を基本的に完了することができます。もちろん、最適化できる点が2つあります。

まず次のように、別の@LubanScanアノテーションを個別に定義します。

@Retention(RetentionPolicy.RUNTIME)
@Import(LubanImportBeanDefinitionRegistrar.class)
public @interface LubanScan {
}

このようにして、@ LubanScanをAppConfigで直接使用できます。

次に、LubanImportBeanDefinitionRegistrarでマッパーをスキャンできます。LubanImportBeanDefinitionRegistrarでは、AnnotationMetadataを介して対応する@LubanScanアノテーションを取得できるため、@ LubanScanに値を設定して、スキャンするパッケージパスを指定できます。次に、LubanImportBeanDefinitionRegistrarで設定されたパッケージパスを取得し、このパスの下のすべてのマッパーをスキャンして、BeanDefinitionを生成し、Springコンテナーに配置します。

これで、これまでのところ、SpringによるMybatisの統合の中心的な原則は終わりです。

  1. MybatisのプロキシオブジェクトからBeanオブジェクトを生成するLubanFactoryBeanを定義します
  2. LubanImportBeanDefinitionRegistrarを定義して、異なるMapperオブジェクトのLubanFactoryBeanを生成します
  3. Springの起動時にLubanImportBeanDefinitionRegistrarのロジックを実行する@LubanScanを定義し、パッケージパスを指定する

上記の3つの要素はそれぞれ
org.mybatis.springにあり
ます。4
. MapperFactoryBean 5. MapperScannerRegistrar 6. @MapperScan

完全なビデオ説明アドレス:春の統合Mybatisビデオ

春の統合について質問がある場合は、コメント領域にメッセージを残すこともできます。そうすれば、ブロガーができるだけ早く回答します。

おすすめ

転載: blog.csdn.net/Lubanjava/article/details/106192543