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]にあります。