1. 概要

この簡単な記事では、プロトタイプBeanをシングルトンインスタンスに注入するさまざまなアプローチを紹介します。 ユースケースと各シナリオの長所/短所について説明します。

デフォルトでは、SpringBeanはシングルトンです。 異なるスコープのBeanをワイヤリングしようとすると、問題が発生します。 たとえば、プロトタイプBeanをシングルトンに変換します。 これはスコープ付きBeanインジェクション問題として知られています。

Beanスコープの詳細については、この記事から始めるとよいでしょう

2. プロトタイプBeanインジェクションの問題

問題を説明するために、次のBeanを構成しましょう。

@Configuration
public class AppConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public PrototypeBean prototypeBean() {
        return new PrototypeBean();
    }

    @Bean
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }
}

最初のBeanにはプロトタイプスコープがあり、もう1つはシングルトンであることに注意してください。

次に、プロトタイプスコープのbeanをシングルトンに注入し、 getPrototype Bean()メソッドを使用して公開します。

public class SingletonBean {

    // ..

    @Autowired
    private PrototypeBean prototypeBean;

    public SingletonBean() {
        logger.info("Singleton instance created");
    }

    public PrototypeBean getPrototypeBean() {
        logger.info(String.valueOf(LocalTime.now()));
        return prototypeBean;
    }
}

次に、 ApplicationContext をロードして、シングルトンbeanを2回取得します。

public static void main(String[] args) throws InterruptedException {
    AnnotationConfigApplicationContext context 
      = new AnnotationConfigApplicationContext(AppConfig.class);
    
    SingletonBean firstSingleton = context.getBean(SingletonBean.class);
    PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
    
    // get singleton bean instance one more time
    SingletonBean secondSingleton = context.getBean(SingletonBean.class);
    PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();

    isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}

コンソールからの出力は次のとおりです。

Singleton Bean created
Prototype Bean created
11:06:57.894
// should create another prototype bean instance here
11:06:58.895

両方のBeanは、アプリケーションコンテキストの起動時に一度だけ初期化されました。

3. ApplicationContextを挿入します

ApplicationContextをBeanに直接注入することもできます。

これを実現するには、 @Autowire アノテーションを使用するか、ApplicationContextAwareインターフェイスを実装します。

public class SingletonAppContextBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public PrototypeBean getPrototypeBean() {
        return applicationContext.getBean(PrototypeBean.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
      throws BeansException {
        this.applicationContext = applicationContext;
    }
}

getPrototypeBean()メソッドが呼び出されるたびに、ProtocolBeanの新しいインスタンスがApplicationContextから返されます。

ただし、このアプローチには重大な欠点があります。コンテナーに直接依存関係を要求するため、制御の反転の原則と矛盾します。

また、SingletonAppcontextBeanクラス内のapplicationContextからプロトタイプBeanをフェッチします。 これは、コードをSpringFrameworkに結合することを意味します。

4. メソッドインジェクション

この問題を解決する別の方法は、@Lookupアノテーションを使用したメソッドインジェクションです。

@Component
public class SingletonLookupBean {

    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null;
    }
}

Springは、@Lookupで注釈が付けられたgetPrototypeBean()メソッドをオーバーライドします。次に、Beanをアプリケーションコンテキストに登録します。 getPrototypeBean()メソッドを要求するたびに、新しいProtocolBeanインスタンスが返されます。

CGLIBを使用して、アプリケーションコンテキストからProtocolBeanをフェッチするバイトコードを生成します。

5. javax.inject API

セットアップと必要な依存関係については、このスプリング配線の記事で説明しています。

シングルトンbeanは次のとおりです。

public class SingletonProviderBean {

    @Autowired
    private Provider<PrototypeBean> myPrototypeBeanProvider;

    public PrototypeBean getPrototypeInstance() {
        return myPrototypeBeanProvider.get();
    }
}

Provider interface を使用して、プロトタイプBeanを注入します。 getPrototypeInstance()メソッド呼び出しごとに、myPrototypeBeanProvider。g et()メソッドはProtocolBeanの新しいインスタンスを返します。

6. スコーププロキシ

デフォルトでは、Springはインジェクションを実行するための実際のオブジェクトへの参照を保持しています。 ここでは、実際のオブジェクトを依存オブジェクトに接続するためのプロキシオブジェクトを作成します。

プロキシオブジェクトのメソッドが呼び出されるたびに、プロキシは、実際のオブジェクトの新しいインスタンスを作成するか、既存のインスタンスを再利用するかを自分で決定します。

これを設定するには、 Appconfig クラスを変更して、新しい@Scopeアノテーションを追加します。

@Scope(
  value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
  proxyMode = ScopedProxyMode.TARGET_CLASS)

デフォルトでは、SpringはCGLIBライブラリを使用してオブジェクトを直接サブクラス化します。 CGLIBの使用を回避するために、ScopedProxyMode。INTERFACESを使用してプロキシモードを構成し、代わりにJDK動的プロキシを使用できます。

7. ObjectFactoryインターフェース

春は ObjectFactory 指定されたタイプのオンデマンドオブジェクトを生成するためのインターフェイス:

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;

    public PrototypeBean getPrototypeInstance() {
        return prototypeBeanObjectFactory.getObject();
    }
}

getPrototypeInstance()メソッドを見てみましょう。 getObject()は、リクエストごとにProtocolBeanの新しいインスタンスを返します。 ここでは、プロトタイプの初期化をより細かく制御できます。

また、ObjectFactoryはフレームワークの一部です。 これは、このオプションを使用するために追加のセットアップを回避することを意味します。

8. java .util.Function を使用して、実行時にBeanを作成します

もう1つのオプションは、実行時にプロトタイプBeanインスタンスを作成することです。これにより、インスタンスにパラメーターを追加することもできます。

この例を見るには、ProtocolBeanクラスに名前フィールドを追加しましょう。

public class PrototypeBean {
    private String name;
    
    public PrototypeBean(String name) {
        this.name = name;
        logger.info("Prototype instance " + name + " created");
    }

    //...   
}

次に、 java .util.Function インターフェイスを使用して、beanファクトリをシングルトンbeanに挿入します。

public class SingletonFunctionBean {
    
    @Autowired
    private Function<String, PrototypeBean> beanFactory;
    
    public PrototypeBean getPrototypeInstance(String name) {
        PrototypeBean bean = beanFactory.apply(name);
        return bean;
    }

}

最後に、構成でファクトリBean、プロトタイプ、およびシングルトンBeanを定義する必要があります。

@Configuration
public class AppConfig {
    @Bean
    public Function<String, PrototypeBean> beanFactory() {
        return name -> prototypeBeanWithParam(name);
    } 

    @Bean
    @Scope(value = "prototype")
    public PrototypeBean prototypeBeanWithParam(String name) {
       return new PrototypeBean(name);
    }
    
    @Bean
    public SingletonFunctionBean singletonFunctionBean() {
        return new SingletonFunctionBean();
    }
    //...
}

9. テスト

次に、ObjectFactoryインターフェイスを使用してケースを実行するための簡単なJUnitテストを作成しましょう。

@Test
public void givenPrototypeInjection_WhenObjectFactory_ThenNewInstanceReturn() {

    AbstractApplicationContext context
     = new AnnotationConfigApplicationContext(AppConfig.class);

    SingletonObjectFactoryBean firstContext
     = context.getBean(SingletonObjectFactoryBean.class);
    SingletonObjectFactoryBean secondContext
     = context.getBean(SingletonObjectFactoryBean.class);

    PrototypeBean firstInstance = firstContext.getPrototypeInstance();
    PrototypeBean secondInstance = secondContext.getPrototypeInstance();

    assertTrue("New instance expected", firstInstance != secondInstance);
}

テストを正常に起動した後、 getPrototypeInstance()メソッドが呼び出されるたびに、新しいプロトタイプBeanインスタンスが作成されたことがわかります。

10. 結論

この短いチュートリアルでは、プロトタイプBeanをシングルトンインスタンスに注入するいくつかの方法を学びました。

いつものように、このチュートリアルの完全なコードはGitHubプロジェクトにあります。