春のカスタムスコープ

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