Springのカスタムスコープ
1. 概要
すぐに使用できるSpringは、任意のSpringアプリケーションで使用できる2つの標準Beanスコープ(“singleton”と“prototype”)に加えて、3つの追加のBeanスコープ(“request”、“session”)を提供します。 、および“globalSession”)は、Web対応アプリケーションでのみ使用します。
標準のBeanスコープをオーバーライドすることはできません。一般に、Web対応スコープをオーバーライドすることは悪い習慣と見なされています。 ただし、提供されたスコープにある機能とは異なるまたは追加の機能を必要とするアプリケーションがある場合があります。
たとえば、マルチテナントシステムを開発している場合、各テナントに特定のBeanまたはBeanのセットの個別のインスタンスを提供することができます。 Springは、このようなシナリオのカスタムスコープを作成するメカニズムを提供します。
このクイックチュートリアルでは、how to create, register, and use a custom scope in a Spring applicationを示します。
2. カスタムスコープクラスの作成
カスタムスコープを作成するには、we must implement the Scope interfaceを使用します。 その際、スコープは複数のBeanファクトリで同時に使用できるため、ensure that the implementation is thread-safeも必要です。
2.1. スコープオブジェクトとコールバックの管理
カスタムScopeクラスを実装するときに最初に考慮すべきことの1つは、スコープ付きオブジェクトと破棄コールバックをどのように格納および管理するかです。 これは、たとえば、マップまたは専用クラスを使用して実行できます。
この記事では、同期マップを使用してスレッドセーフな方法でこれを行います。
カスタムスコープクラスの定義を始めましょう。
public class TenantScope implements Scope {
private Map scopedObjects
= Collections.synchronizedMap(new HashMap());
private Map destructionCallbacks
= Collections.synchronizedMap(new HashMap());
...
}
2.2. スコープからのオブジェクトの取得
スコープから名前でオブジェクトを取得するには、getObjectメソッドを実装しましょう。 JavaDocが述べているように、if the named object does not exist in the scope, this method must create and return a new object。
実装では、名前付きオブジェクトがマップにあるかどうかを確認します。 そうである場合はそれを返し、そうでない場合はObjectFactoryを使用して新しいオブジェクトを作成し、それをマップに追加して返します。
@Override
public Object get(String name, ObjectFactory> objectFactory) {
if(!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
Scopeインターフェースによって定義された5つのメソッドのうち、記述された動作のonly the get method is required to have a full implementation。 他の4つの方法はオプションであり、機能をサポートする必要がない、またはサポートできない場合は、UnsupportedOperationExceptionをスローする可能性があります。
2.3. 破壊コールバックの登録
また、registerDestructionCallbackメソッドを実装する必要があります。 このメソッドは、名前付きオブジェクトが破棄されたとき、またはスコープ自体がアプリケーションによって破棄されたときに実行されるコールバックを提供します。
@Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
2.4. スコープからのオブジェクトの削除
次に、removeメソッドを実装しましょう。このメソッドは、名前付きオブジェクトをスコープから削除し、登録済みの破棄コールバックも削除して、削除されたオブジェクトを返します。
@Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
it is the caller’s responsibility to actually execute the callback and destroy the removed objectに注意してください。
2.5. 会話IDの取得
それでは、getConversationIdメソッドを実装しましょう。 スコープが会話IFの概念をサポートしている場合、ここでそれを返します。 それ以外の場合、規則はnullを返すことです。
@Override
public String getConversationId() {
return "tenant";
}
2.6. コンテキストオブジェクトの解決
最後に、resolveContextualObjectメソッドを実装しましょう。 スコープが複数のコンテキストオブジェクトをサポートしている場合は、それぞれをキー値に関連付け、指定されたkeyパラメータに対応するオブジェクトを返します。 それ以外の場合、規則はnullを返すことです。
@Override
public Object resolveContextualObject(String key) {
return null;
}
3. カスタムスコープの登録
Springコンテナに新しいスコープを認識させるには、register it through the registerScope method on a ConfigurableBeanFactory instanceを実行する必要があります。 このメソッドの定義を見てみましょう。
void registerScope(String scopeName, Scope scope);
最初のパラメーターscopeNameは、スコープを一意の名前で識別/指定するために使用されます。 2番目のパラメーターscopeは、登録して使用するカスタムScope実装の実際のインスタンスです。
カスタムBeanFactoryPostProcessorを作成し、ConfigurableListableBeanFactoryを使用してカスタムスコープを登録しましょう。
public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope("tenant", new TenantScope());
}
}
それでは、BeanFactoryPostProcessorの実装をロードするSpring構成クラスを作成しましょう。
@Configuration
public class TenantScopeConfig {
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
return new TenantBeanFactoryPostProcessor();
}
}
4. カスタムスコープの使用
カスタムスコープを登録したので、singleton(デフォルトスコープ)以外のスコープを使用する他のBeanの場合と同じように、@Scopeを使用して任意のBeanに適用できます。 ■注釈と名前によるカスタムスコープの指定。
単純なTenantBeanクラスを作成しましょう—このタイプのテナントスコープのBeanをすぐに宣言します。
public class TenantBean {
private final String name;
public TenantBean(String name) {
this.name = name;
}
public void sayHello() {
System.out.println(
String.format("Hello from %s of type %s",
this.name,
this.getClass().getName()));
}
}
このクラスでは、クラスレベルの@Componentおよび@Scopeアノテーションを使用しなかったことに注意してください。
それでは、構成クラスでテナントスコープのBeanを定義しましょう。
@Configuration
public class TenantBeansConfig {
@Scope(scopeName = "tenant")
@Bean
public TenantBean foo() {
return new TenantBean("foo");
}
@Scope(scopeName = "tenant")
@Bean
public TenantBean bar() {
return new TenantBean("bar");
}
}
5. カスタムスコープのテスト
ApplicationContextをロードし、Configurationクラスを登録し、テナントスコープのBeanを取得して、カスタムスコープ構成を実行するテストを作成しましょう。
@Test
public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
try{
ctx.register(TenantScopeConfig.class);
ctx.register(TenantBeansConfig.class);
ctx.refresh();
TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class);
foo.sayHello();
TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class);
bar.sayHello();
Map foos = ctx.getBeansOfType(TenantBean.class);
assertThat(foo, not(equalTo(bar)));
assertThat(foos.size(), equalTo(2));
assertTrue(foos.containsValue(foo));
assertTrue(foos.containsValue(bar));
BeanDefinition fooDefinition = ctx.getBeanDefinition("foo");
BeanDefinition barDefinition = ctx.getBeanDefinition("bar");
assertThat(fooDefinition.getScope(), equalTo("tenant"));
assertThat(barDefinition.getScope(), equalTo("tenant"));
}
finally {
ctx.close();
}
}
テストの出力は次のとおりです。
Hello from foo of type org.example.customscope.TenantBean
Hello from bar of type org.example.customscope.TenantBean
6. 結論
このクイックチュートリアルでは、Springでカスタムスコープを定義、登録、および使用する方法を示しました。
カスタムスコープの詳細については、Spring Framework Referenceを参照してください。 また、Spring Framework repository on GitHub内のさまざまなScopeクラスのSpringの実装を確認することもできます。
いつものように、この記事で使用されているコードサンプルはGitHub projectにあります。