Guten Tag, Chabrowiten!
In diesem Artikel schlage ich vor, eines der Probleme zu diskutieren, die bei Projekten mit dem Spring-Framework häufig auftreten. Das in diesem Artikel beschriebene Problem tritt aufgrund eines der typischen Fehler in Federkonfigurationen auf. Sie müssen nicht versuchen, einen solchen Fehler in der Konfiguration zu machen, und daher ist dieser Fehler recht häufig.
Problemstellung
Das in diesem Artikel vorgestellte Problem hängt mit der falschen Konfiguration von Beans im aktuellen Anwendungskontext zusammen, die aus anderen Anwendungskontexten stammen. Ein solches Problem kann bei einer großen industriellen Anwendung auftreten, die aus vielen Gläsern besteht, von denen jedes seinen eigenen Anwendungskontext hat, der Frühlingsbohnen enthält.
Aufgrund einer falschen Konfiguration erhalten wir mehrere Kopien von Beans mit einem unvorhersehbaren Status, selbst wenn sie den Gültigkeitsbereich Singleton haben. Darüber hinaus kann das gedankenlose Kopieren von Beans dazu führen, dass mehr als ein Dutzend Kopien aller Beans eines JARs in der Anwendung erstellt werden, was mit Problemen bei der Anwendungsleistung und einer Verlängerung der Anwendungsstartzeit behaftet ist.
Ein Beispiel für die Verwendung einer Bean aus einem externen Anwendungskontext in der aktuellen Version
Stellen Sie sich vor, wir entwickeln in einem der Anwendungsmodule, in denen es viele andere Module gibt, und jedes der Module hat seinen eigenen Anwendungskontext. Eine solche Anwendung sollte ein Modul haben, in dem Instanzen des Anwendungskontexts aller Anwendungsmodule erstellt werden.

Angenommen, im Anwendungskontext eines der externen Module wird eine Instanz der Bean der NumberGenerator-Klasse erstellt, die wir in unser Modul aufnehmen möchten. Angenommen, die NumberGenerator-Klasse befindet sich im Paket org.example.kruchon.generators, in dem einige Klassen gespeichert sind, die Werte generieren.

Diese Bean hat einen Status - das Feld int count.
package org.example.kruchon.calculators public class NumberGenerator { private int count = 0; public synchronized int next() { return count++; } }
Eine Instanz dieser Bean wird in der Unterkonfiguration GeneratorsConfiguration erstellt.
@Configuration public class GeneratorsConfiguration { @Bean public NumberGenerator numberGenerator() { return new NumberGenerator(); } ... }
Auch im externen Anwendungskontext gibt es eine Hauptkonfiguration, in die alle Unterkonfigurationen des externen Moduls importiert werden.
@Configuration @Import({GeneratorsConfiguration.class, ...}) public class ExternalContextConfiguration { ... }
Jetzt werde ich einige Beispiele geben, in denen die Singleton-Bean der NumberGenerator-Klasse in der Konfiguration des aktuellen Anwendungskontexts falsch konfiguriert ist.
Falsche Konfiguration 1. Importieren der Hauptkonfiguration des externen Anwendungskontexts
Die schlimmste Entscheidung, die es geben könnte.
@Configuration @Import(ExternalContextConfiguration.class) public class CurrentContextConfiguration { ... }
- Die Anwendung erstellt alle Beans-Instanzen aus dem externen Anwendungskontext neu. Mit anderen Worten, es wird eine Kopie des gesamten externen Moduls erstellt, was sich auf den Speicherverbrauch, die Leistung und die Startzeit der Anwendung auswirkt.
- Holen Sie sich eine Kopie von NumberGenerator im aktuellen Anwendungskontext. Eine Kopie von NumberGenerator hat einen eigenen Wert für das Zählfeld, der nicht mit der ersten Instanz von NumberGenerator übereinstimmt. Diese Inkonsistenz führt zu schwer zu debuggenden Fehlern in der Anwendung.
Falsche Konfiguration 2. Importieren Sie die Unterkonfiguration des externen Anwendungskontexts
Die zweite Option ist falsch und in der Praxis häufig anzutreffen.
@Configuration @Import(GeneratorsConfiguration.class) public class CurrentContextConfiguration { ... }
In dieser Ausführungsform wird keine vollständige Kopie des externen Moduls mehr erstellt, wir erhalten jedoch erneut die zweite Bean der NumberGenerator-Klasse.
Falsche Konfiguration 3. Suchen Sie die Injektion direkt in die Bean, in der NumberGenerator verwendet werden soll
public class OrderFactory { private final NumberGenerator numberGenerator; public OrderFactory() { ApplicationContext externalApplicationContext = getExternalContext(); numberGenerator = externalApplicationContext.getBean(NumberGenerator.class); } public Order create() { Order order = new Order(); int id = numberGenerator.next(); order.setId(id); order.setCreatedDate(new Date()); return order; } private ApplicationContext getExternalContext(){ ... } }
Bei dieser Methode kann die Duplizierung einer Bean mit dem Gültigkeitsbereich Singleton als gelöst betrachtet werden. In der Tat verwenden wir die Bean jetzt aus einem anderen Anwendungskontext wieder und erstellen sie nicht neu!
Aber so:
- Kompliziert die entwickelte Klasse und ihre Unit-Tests.
- Schließt die automatische Implementierung der Bean der NumberGenerator-Klasse in den Beans des aktuellen Moduls aus.
- In allgemeinen Fällen ist es nicht üblich, lookUp zum Injizieren einer Singleton-Bean zu verwenden.
Daher ist eine solche Lösung eher eine ungeschickte Problemumgehung als eine rationale Lösung eines Problems.
Überlegen Sie, wie Sie eine Bean in einem externen Anwendungskontext ordnungsgemäß konfigurieren.
Lösung 1. Holen Sie sich Bean aus dem externen Anwendungskontext in der Konfiguration
Diese Methode ist dem dritten Beispiel einer falschen Konfiguration sehr ähnlich, mit einem Unterschied: Wir erhalten eine Bean, indem wir LookUp aus dem externen Kontext in der Konfiguration und nicht direkt in die Bean erstellen.
@Configuration public class CurrentContextConfiguration { @Bean public NumberGenerator numberGenerator() { ApplicationContext externalApplicationContext = getExternalContext(); return externalApplicationContext.getBean(NumberGenerator.class); } private ApplicationContext getExternalContext(){ ... } }
Jetzt können wir diese Bean automatisch in Beans aus unserem eigenen Modul einbetten.
Lösung 2. Machen Sie den externen Anwendungskontext übergeordnet
Es ist wahrscheinlich, dass die Funktionalität des aktuellen Moduls die Funktionalität des externen Moduls erweitert. Es kann vorkommen, dass in einem der externen Module zusätzliche Beans entwickelt werden, die der gesamten Anwendung gemeinsam sind, und in anderen Modulen diese Beans verwendet werden. In diesem Fall ist es logisch anzugeben, dass das externe Modul dem vorherigen übergeordnet ist. In diesem Fall können alle Beans des übergeordneten Moduls im aktuellen Modul verwendet werden, und dann muss die
Bean des übergeordneten Moduls nicht in der Konfiguration des aktuellen Anwendungskontexts konfiguriert werden.
Es ist möglich, eine übergeordnete Beziehung anzugeben, wenn eine Kontextinstanz mithilfe des Konstruktors mit dem übergeordneten Parameter erstellt wird:
public AbstractApplicationContext(ApplicationContext parent) { ... }
Oder verwenden Sie den Setter:
public void setParent(ApplicationContext parent) { ... }
Wenn der Anwendungskontext in XML deklariert ist, können wir den Konstruktor verwenden:
public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { ... }
Fazit
Seien Sie daher beim Konfigurieren von Spring Beans vorsichtig, befolgen Sie die Empfehlungen im Artikel und versuchen Sie, Beans mit Scope Singleton nicht zu kopieren. Gerne beantworte ich Ihre Fragen!