SpringでプロトタイプBeanをシングルトンインスタンスに注入する

1概要

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

デフォルトでは、Spring Beanはシングルトンです。異なるスコープの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はプロトタイプスコープを持ち、他のBeanはシングルトンです。

それでは、プロトタイプスコープのBeanをシングルトンにインジェクトしてから、 getPrototypeBean() メソッドを介して公開します。

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が1回だけ初期化されました。

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() メソッドが呼び出されるたびに、 PrototypeBean の新しいインスタンスが ApplicationContext から返されます。

  • しかしながら、このアプローチは深刻なデメリットを持っています** コンテナーからの依存関係を直接要求するので、制御の逆転の原則と矛盾します。

また、 SingletonAppcontextBean クラス内の applicationContext からプロトタイプBeanを取り出します。これは、コードをSpring Frameworkに結合することを意味します。

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

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

@Component
public class SingletonLookupBean {

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

Springは、 @Lookupでアノテーションが付けられた getPrototypeBean()__メソッドをオーバーライドします。次に、Beanをアプリケーションコンテキストに登録します。

getPrototypeBean() メソッドを要求するたびに、新しい PrototypeBean インスタンスを返します。

  • CGLIBを使用して、アプリケーションコンテキストから PrototypeBean を取得するためのバイトコードを生成します。

5 javax.inject API

必要な依存関係と一緒にセットアップはこのリンクで説明されています:/spring-annotations-resource-inject-autowire[春の配線]の記事。

これがシングルトンBeanです。

public class SingletonProviderBean {

    @Autowired
    private Provider<PrototypeBean> myPrototypeBeanProvider;

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

プロトタイプBeanをインジェクトするために Provider interface を使用します。 getPrototypeInstance()メソッド呼び出しごとに、 myPrototypeBeanProvider . g et()メソッドは PrototypeBean の新しいインスタンスを返します。

6. スコープ付きプロキシ

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

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

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

@Scope(
  value = ConfigurableBeanFactory.SCOPE__PROTOTYPE,
  proxyMode = ScopedProxyMode.TARGET__CLASS)

デフォルトでは、SpringはCGLIBライブラリを使用してオブジェクトを直接サブクラス化します。

CGLIBの使用を避けるために、代わりにJDK動的プロキシを使用するために _ScopedProxyMode . _ INTERFACESでプロキシモードを設定できます。

7. ObjectFactory インターフェース

Springは、オンデマンドオブジェクトを生成するためにhttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/ObjectFactoryCreatingFactoryBean.html[ObjectFactory <T>]インターフェースを提供します。与えられたタイプのもの

public class SingletonObjectFactoryBean {

    @Autowired
    private ObjectFactory<PrototypeBean> prototypeBeanObjectFactory;

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

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

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

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

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

この例を見るために、 PrototypeBean クラスにnameフィールドを追加しましょう。

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、およびシングルトン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をシングルトンインスタンスにインジェクトするいくつかの方法を学びました。

いつものように、このチュートリアルの完全なコードはhttps://github.com/eugenp/tutorials/tree/master/spring-core[GitHub project]にあります。