春のカスタムスコープ

1概要

標準の状態で、Springは任意のSpringアプリケーションで使用できる2つの標準Beanスコープ( "singleton" "prototype" )と3つの追加のBeanスコープ( "request" "session" )を提供します。 Web対応アプリケーションでのみ使用するための "globalSession" )。

標準のBeanスコープをオーバーライドすることはできません。Web対応のスコープをオーバーライドするのは一般的に不適切な方法と考えられています。ただし、提供されているスコープとは異なるまたは追加の機能を必要とするアプリケーションがあるかもしれません。

たとえば、マルチテナントシステムを開発している場合は、テナントごとに特定のBeanまたは一連のBeanの個別のインスタンスを提供することができます。 Springはこのようなシナリオに対してカスタムスコープを作成するためのメカニズムを提供します。

このクイックチュートリアルでは、Springアプリケーションでカスタムスコープを作成、登録、および使用する方法を説明します。

2カスタムスコープクラスの作成

カスタムスコープを作成するためには、 Scope を実装する必要があります。インタフェース 。その際、スコープは複数のBeanファクトリによって同時に使用される可能性があるため、実装がスレッドセーフであることも確認する必要があります。

2.1. スコープオブジェクトとコールバックの管理

カスタム Scope クラスを実装するときに最初に考慮すべきことの1つは、スコープ付きオブジェクトと破棄コールバックを格納し管理する方法です。これは、たとえばマップまたは専用クラスを使用して実行できます。

この記事では、同期マップを使用してスレッドセーフな方法でこれを行います。

カスタムスコープクラスの定義を始めましょう。

public class TenantScope implements Scope {
    private Map<String, Object> scopedObjects
      = Collections.synchronizedMap(new HashMap<String, Object>());
    private Map<String, Runnable> destructionCallbacks
      = Collections.synchronizedMap(new HashMap<String, Runnable>());
...
}

2.2. スコープからのオブジェクトの取得

スコープから名前でオブジェクトを取得するには、 getObject メソッドを実装しましょう。 JavaDocが述べているように、 名前付きオブジェクトがスコープ内に存在しない場合、このメソッドは新しいオブジェクトを作成して返す必要があります

この実装では、名前付きオブジェクトがマップ内にあるかどうかを確認します。そうであればそれを返し、そうでなければ ObjectFactory を使って新しいオブジェクトを作成し、それをマップに追加して返します。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    if(!scopedObjects.containsKey(name)) {
        scopedObjects.put(name, objectFactory.getObject());
    }
    return scopedObjects.get(name);
}

Scope インタフェースによって定義された5つのメソッドのうち、記述された動作の完全な実装を持つためには get メソッドだけが必要です。他の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);
}
  • 実際にコールバックを実行し、削除されたオブジェクトを破棄するのは呼び出し側の責任です。

2.5. 会話IDを取得する

それでは、 getConversationId メソッドを実装しましょう。スコープが会話IDの概念をサポートしている場合は、それをここに返します。

そうでなければ、規約は null を返すことです:

@Override
public String getConversationId() {
    return "tenant";
}

** 2.6. コンテキストオブジェクトの解決

最後に、 resolveContextualObject メソッドを実装しましょう。スコープが複数のコンテキストオブジェクトをサポートしている場合は、それぞれをキー値に関連付け、提供された key パラメータに対応するオブジェクトを返します。そうでなければ、規約は null を返すことです:

@Override
public Object resolveContextualObject(String key) {
    return null;
}

3カスタムスコープを登録する

Springコンテナに新しいスコープを認識させるには、 ConfigurableBeanFactory インスタンスの registerScope メソッドを通して** 登録する必要があります。このメソッドの定義を見てみましょう。

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カスタムスコープを使用する

カスタムスコープを登録したので、 @ Scope アノテーションを使用してカスタムを指定することで、 singleton (デフォルトスコープ)以外のスコープを使用する他のBeanと同じように、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<String, TenantBean> 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.baeldung.customscope.TenantBean
Hello from bar of type org.baeldung.customscope.TenantBean

6. 結論

このクイックチュートリアルでは、Springでカスタムスコープを定義、登録、および使用する方法を示しました。

カスタムスコープについての詳細はhttp://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-factory-scopes -custom[Spring Framework Reference]をご覧ください。 GitHubのSpring Frameworkリポジトリ で、Springのさまざまな Scope クラスの実装を調べることもできます。

いつものように、この記事で使われているコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/spring-all[GitHubプロジェクト]にあります。