Benutzerdefinierter Bereich im Frühjahr

1. Übersicht

Standardmäßig bietet Spring zwei Standard-Bean-Bereiche ( "Singleton" und "Prototyp" ), die in jeder Spring-Anwendung verwendet werden können, sowie drei zusätzliche Bean-Bereiche ( "Request" , "Session" und "GlobalSession" ) zur Verwendung Nur in webfähigen Anwendungen.

Die Standard-Bean-Bereiche können nicht überschrieben werden, und es wird allgemein als schlechte Praxis angesehen, die webfähigen Bereiche zu überschreiben. Möglicherweise benötigen Sie jedoch eine Anwendung, die andere oder zusätzliche Funktionen erfordert als die 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 einer bestimmten Bean oder eines Satzes von Beans bereitstellen. Spring bietet einen Mechanismus zum Erstellen benutzerdefinierter Bereiche für solche Szenarien.

In diesem kurzen Tutorial zeigen wir Ihnen, wie Sie einen benutzerdefinierten Bereich in einer Spring-Anwendung erstellen, registrieren und verwenden .

2. Erstellen einer benutzerdefinierten Bereichsklasse

Um einen benutzerdefinierten Bereich zu schaffen, müssen wir die Umsetzung Scope - Schnittstelle . Dabei müssen wir auch sicherstellen, dass die Implementierung threadsicher ist, 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 benutzerdefinierten Scope- 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 Methode getObject . Wie im JavaDoc angegeben, muss diese Methode ein neues Objekt erstellen und zurückgeben , wenn das benannte Objekt nicht im Bereich vorhanden ist .

In unserer Implementierung prüfen wir, ob sich das benannte Objekt in unserer Karte befindet. Wenn dies der Fall ist, geben wir es zurück. Wenn nicht, verwenden wir die ObjectFactory , 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 von der Scope- Schnittstelle definierten Methoden ist nur die get- Methode erforderlich, um das beschriebene Verhalten vollständig zu implementieren . Die anderen vier Methoden sind optional und lösen möglicherweise UnsupportedOperationException aus, wenn sie eine Funktionalität nicht benötigen oder nicht unterstützen können.

2.3. Registrieren eines Destruction Callback

Wir müssen auch die registerDestructionCallback- Methode implementieren . Diese Methode stellt einen Rückruf bereit, der ausgeführt werden soll, wenn das benannte Objekt zerstört wird oder wenn der Bereich 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 Methode remove , mit der das benannte Objekt aus dem Bereich entfernt und der registrierte Rückruf für die Zerstörung entfernt wird, wobei das entfernte Objekt zurückgegeben wird:

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

Beachten Sie, dass es in der Verantwortung des Anrufers liegt, den Rückruf tatsächlich auszuführen und das entfernte Objekt zu zerstören .

2.5. Abrufen der Gesprächs-ID

Lassen Sie uns nun die Methode getConversationId implementieren . Wenn Ihr Bereich das Konzept einer Konversations-ID unterstützt, geben Sie diese hier zurück. Andernfalls lautet die Konvention null :

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

2.6. Kontextobjekte auflösen

Zuletzt implementieren wir die Methode resolveContextualObject . Wenn Ihr Bereich mehrere Kontextobjekte unterstützt, würden Sie jedem einen Schlüsselwert zuordnen und das Objekt zurückgeben, das dem angegebenen Schlüsselparameter entspricht . Andernfalls lautet die Konvention null :

@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 Sie ihn über die registerScope- Methode in einer ConfigurableBeanFactory- Instanz registrieren . 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 Scope- Implementierung, die Sie registrieren und verwenden möchten.

Erstellen wir einen benutzerdefinierten BeanFactoryPostProcessor und registrieren unseren benutzerdefinierten Bereich mithilfe einer ConfigurableListableBeanFactory :

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 unsere BeanFactoryPostProcessor- Implementierung lä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 auf jede andere Bean, die einen anderen Bereich als Singleton (den Standardbereich) verwendet. Verwenden Sie dazu die Annotation @Scope und geben Sie unseren benutzerdefinierten Bereich an namentlich.

Lassen Sie uns eine einfache TenantBean- Klasse erstellen - wir werden gleich Bohnen mit Tenant-Gültigkeitsbereich dieses Typs deklarieren:

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 die Annotationen @Component und @Scope auf Klassenebene nicht 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 wir einen Test, um unsere benutzerdefinierte Bereichskonfiguration durchzuführen, indem wir einen ApplicationContext laden , unsere Konfigurationsklassen 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 die Ausgabe unseres Tests ist:

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

6. Fazit

In diesem kurzen Tutorial haben wir gezeigt, wie Sie im Frühjahr einen benutzerdefinierten Bereich definieren, registrieren und verwenden.

Weitere Informationen zu benutzerdefinierten Bereichen finden Sie in der Spring Framework-Referenz. Sie können sich auch die Spring-Implementierungen verschiedener Scope- Klassen im Spring Framework-Repository auf GitHub ansehen .

Wie üblich finden Sie die in diesem Artikel verwendeten Codebeispiele im GitHub-Projekt.