Custom Scope im Frühling

Custom Scope im Frühjahr

1. Überblick

Standardmäßig bietet Spring zwei Standard-Bean-Bereiche (“singleton” und“prototype”), die in jeder Spring-Anwendung verwendet werden können, sowie drei zusätzliche Bean-Bereiche (“request”,“session” und“globalSession”) nur zur Verwendung in webfähigen Anwendungen.

Die Standard-Bean-Bereiche können nicht überschrieben werden. Es wird allgemein als schlechte Praxis angesehen, die webfähigen Bereiche zu überschreiben. Möglicherweise ist jedoch für eine Anwendung eine andere oder zusätzliche Funktionalität erforderlich als in den bereitgestellten Bereichen.

Wenn Sie beispielsweise ein System mit mehreren Mandanten entwickeln, möchten Sie möglicherweise für jeden Mandanten eine separate Instanz eines bestimmten Beans oder eines Satzes von Beans bereitstellen. Spring bietet einen Mechanismus zum Erstellen benutzerdefinierter Bereiche für solche Szenarien.

In diesem kurzen Tutorial werden wirhow to create, register, and use a custom scope in a Spring application demonstrieren.

2. Erstellen einer benutzerdefinierten Bereichsklasse

Um einen benutzerdefinierten Bereich zu erstellen, müssen Siewe must implement the Scope interface. Dabei müssen wir auchensure that the implementation is thread-safe verwenden, da Bereiche von mehreren Bean-Fabriken gleichzeitig verwendet werden können.

2.1. Verwalten der Objekte und Rückrufe mit Gültigkeitsbereich

Eines der ersten Dinge, die bei der Implementierung einer benutzerdefiniertenScope-Klasse berücksichtigt werden müssen, ist das Speichern und Verwalten der Objekte mit Gültigkeitsbereich und der Zerstörung von Rückrufen. Dies kann beispielsweise mithilfe einer Karte oder einer dedizierten Klasse erfolgen.

In diesem Artikel wird dies threadsicher mithilfe synchronisierter Karten durchgeführt.

Beginnen wir mit der Definition unserer benutzerdefinierten Bereichsklasse:

public class TenantScope implements Scope {
    private Map scopedObjects
      = Collections.synchronizedMap(new HashMap());
    private Map destructionCallbacks
      = Collections.synchronizedMap(new HashMap());
...
}

2.2. Abrufen eines Objekts aus dem Bereich

Um ein Objekt nach Namen aus unserem Bereich abzurufen, implementieren wir die MethodegetObject. Wie im JavaDoc angegeben,if the named object does not exist in the scope, this method must create and return a new object.

In unserer Implementierung prüfen wir, ob sich das benannte Objekt in unserer Map befindet. Wenn dies der Fall ist, geben wir es zurück. Wenn nicht, verwenden wirObjectFactory, um ein neues Objekt zu erstellen, es unserer Karte hinzuzufügen und es zurückzugeben:

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

Von den fünf durch dieScope-Schnittstelle definierten Methoden sindonly the get method is required to have a full implementation des beschriebenen Verhaltens. Die anderen vier Methoden sind optional und könnenUnsupportedOperationException auslösen, wenn sie eine Funktionalität nicht benötigen oder nicht unterstützen können.

2.3. Registrieren eines Destruction Callback

Wir müssen auch dieregisterDestructionCallback-Methode implementieren. Diese Methode stellt einen Rückruf bereit, der ausgeführt werden soll, wenn das genannte Objekt zerstört wird oder wenn der Gültigkeitsbereich selbst von der Anwendung zerstört wird:

@Override
public void registerDestructionCallback(String name, Runnable callback) {
    destructionCallbacks.put(name, callback);
}

2.4. Entfernen eines Objekts aus dem Bereich

Als nächstes implementieren wir die Methoderemove, mit der das benannte Objekt aus dem Bereich entfernt und der registrierte Zerstörungsrückruf entfernt wird, wobei das entfernte Objekt zurückgegeben wird:

@Override
public Object remove(String name) {
    destructionCallbacks.remove(name);
    return scopedObjects.remove(name);
}

Beachten Sie, dassit is the caller’s responsibility to actually execute the callback and destroy the removed object.

2.5. Abrufen der Gesprächs-ID

Implementieren wir nun diegetConversationId-Methode. Wenn Ihr Bereich das Konzept einer Konversations-ID unterstützt, geben Sie diese hier zurück. Andernfalls lautet die Konvention,null zurückzugeben:

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

2.6. Kontextobjekte auflösen

Lassen Sie uns abschließend die MethoderesolveContextualObjectimplementieren. Wenn Ihr Bereich mehrere kontextbezogene Objekte unterstützt, würden Sie jedem einen Schlüsselwert zuordnen und das Objekt zurückgeben, das dem angegebenen Parameterkeyentspricht. Andernfalls lautet die Konvention,null zurückzugeben:

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

3. Registrieren des benutzerdefinierten Bereichs

Um den Spring-Container auf Ihren neuen Bereich aufmerksam zu machen, müssen Sieregister it through the registerScope method on a ConfigurableBeanFactory instance eingeben. Werfen wir einen Blick auf die Definition dieser Methode:

void registerScope(String scopeName, Scope scope);

Der erste Parameter,scopeName, wird verwendet, um einen Bereich anhand seines eindeutigen Namens zu identifizieren / anzugeben. Der zweite Parameter,scope, ist eine tatsächliche Instanz der benutzerdefinierten Implementierung vonScope, die Sie registrieren und verwenden möchten.

Erstellen Sie ein benutzerdefiniertesBeanFactoryPostProcessor und registrieren Sie unseren benutzerdefinierten Bereich mit einemConfigurableListableBeanFactory:

public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.registerScope("tenant", new TenantScope());
    }
}

Schreiben wir nun eine Spring-Konfigurationsklasse, die die Implementierung vonBeanFactoryPostProcessorlädt:

@Configuration
public class TenantScopeConfig {

    @Bean
    public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
        return new TenantBeanFactoryPostProcessor();
    }
}

4. Verwenden des benutzerdefinierten Bereichs

Nachdem wir unseren benutzerdefinierten Bereich registriert haben, können wir ihn auf jede unserer Beans anwenden, genau wie bei jeder anderen Bean, die einen anderen Bereich alssingleton (den Standardbereich) verwendet - indem wir@Scopeverwenden. s Anmerkung und Angabe unseres benutzerdefinierten Bereichs nach Namen.

Erstellen wir eine einfacheTenantBean-Klasse. In Kürze werden Bohnen dieses Typs mit Mandantenbereich deklariert:

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()));
    }
}

Beachten Sie, dass wir für diese Klasse nicht die Anmerkungen@Component und@Scope auf Klassenebene verwendet haben.

Definieren wir nun einige Beans mit Mandantenbereich in einer Konfigurationsklasse:

@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. Testen des benutzerdefinierten Bereichs

Schreiben Sie einen Test, um unsere benutzerdefinierte Bereichskonfiguration durchzuführen, indem SieApplicationContext laden, unsereConfiguration-Klassen registrieren und unsere Beans mit Mandantenbereich abrufen:

@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();
    }
}

Und das Ergebnis unseres Tests ist:

Hello from foo of type org.example.customscope.TenantBean
Hello from bar of type org.example.customscope.TenantBean

6. Fazit

In diesem kurzen Lernprogramm haben wir gezeigt, wie Sie einen benutzerdefinierten Bereich in Spring definieren, registrieren und verwenden.

Weitere Informationen zu benutzerdefinierten Bereichen finden Sie inSpring Framework Reference. Sie können sich auch die Spring-Implementierungen verschiedenerScope-Klassen inSpring Framework repository on GitHubansehen.

Wie üblich finden Sie die in diesem Artikel verwendeten Codebeispiele aufGitHub project.