Mit der Java-Konfiguration von Spring muss ich eine Bean mit Prototypbereich mit Konstruktorargumenten erwerben / instanziieren, die nur zur Laufzeit verfügbar sind. Betrachten Sie das folgende Codebeispiel (der Kürze halber vereinfacht):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
wobei die Thing-Klasse wie folgt definiert ist:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Hinweis name
ist final
: Es kann nur über einen Konstruktor geliefert werden und garantiert Unveränderlichkeit. Die anderen Abhängigkeiten sind implementierungsspezifische Abhängigkeiten der Thing
Klasse und sollten nicht bekannt (eng mit der Implementierung des Anforderungshandlers gekoppelt) sein.
Dieser Code funktioniert perfekt mit der Spring XML-Konfiguration, zum Beispiel:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Wie erreiche ich dasselbe mit der Java-Konfiguration? Folgendes funktioniert mit Spring 3.x nicht:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Jetzt könnte ich eine Fabrik erstellen, zB:
public interface ThingFactory {
public Thing createThing(String name);
}
Dies macht jedoch die Verwendung von Spring zum Ersetzen des ServiceLocator- und Factory-Entwurfsmusters zunichte , was für diesen Anwendungsfall ideal wäre.
Wenn Spring Java Config dies könnte, könnte ich Folgendes vermeiden:
- Definieren einer Factory-Schnittstelle
- Definieren einer Factory-Implementierung
- Schreiben von Tests für die Factory-Implementierung
Das ist eine Menge Arbeit (relativ gesehen) für etwas so Triviales, dass Spring es bereits über die XML-Konfiguration unterstützt.
Thing
Implementierung ist tatsächlich komplexer und hat Abhängigkeiten von anderen Beans (ich habe sie der Kürze halber einfach weggelassen). Daher möchte ich nicht, dass die Implementierung des Anforderungshandlers davon erfährt, da dies den Handler eng mit APIs / Beans koppeln würde, die er nicht benötigt. Ich werde die Frage aktualisieren, um Ihre (ausgezeichnete) Frage widerzuspiegeln.@Qualifier
einem Setter mit@Autowired
dem Setter selbst Parameter zuweisen können.@Bean
funktioniert Ihr Beispiel mit . Die@Bean
Methode wird mit den entsprechenden Argumenten aufgerufen, an die Sie übergeben habengetBean(..)
.Antworten:
In einer
@Configuration
Klasse eine@Bean
solche Methodewird verwendet, um eine Bean-Definition zu registrieren und die Factory zum Erstellen der Bean bereitzustellen . Die von ihr definierte Bean wird nur auf Anfrage mithilfe von Argumenten instanziiert, die entweder direkt oder durch Scannen dieser Argumente ermittelt werden
ApplicationContext
.Bei einer
prototype
Bean wird jedes Mal ein neues Objekt erstellt und daher auch die entsprechende@Bean
Methode ausgeführt.Sie können eine Bean
ApplicationContext
über dieBeanFactory#getBean(String name, Object... args)
Methode abrufen , die angibtMit anderen Worten, für diese
prototype
Bean mit Gültigkeitsbereich geben Sie die Argumente an, die nicht im Konstruktor der Bean-Klasse, sondern im@Bean
Methodenaufruf verwendet werden.Dies gilt zumindest für die Spring-Versionen 4+.
quelle
@Bean
Methode nicht auf den manuellen Aufruf beschränken können. Wenn Sie jemals@Autowire Thing
die@Bean
Methode als wahrscheinlich sterbend bezeichnen, wenn Sie den Parameter nicht injizieren können. Das Gleiche gilt, wenn Sie@Autowire List<Thing>
. Ich fand das etwas zerbrechlich.@Autowire
?Mit Spring> 4.0 und Java 8 können Sie dies typsicherer machen:
Verwendung:
Jetzt können Sie Ihre Bean zur Laufzeit erhalten. Dies ist natürlich ein Factory-Muster, aber Sie können beim Schreiben bestimmter Klassen wie z. B. Zeit sparen
ThingFactory
(Sie müssen jedoch benutzerdefiniert schreiben@FunctionalInterface
, um mehr als zwei Parameter zu übergeben).quelle
fabric
stattfactory
meiner schlechten verwendet :)Provider
oder an a übergebenObjectFactory
, oder irre ich mich? Und in meinem Beispiel können Sie ihm einen String-Parameter (oder einen beliebigen Parameter) übergeben@Bean
und mitScope
Anmerkungen versehenThing thing
. Darüber hinaus kann diese Methode privat gemacht werden, um sich zu verstecken und nur die Fabrik zu verlassen.Seit Frühjahr 4.3 gibt es eine neue Möglichkeit, die für dieses Problem genäht wurde.
ObjectProvider - Hiermit können Sie es einfach als Abhängigkeit zu Ihrer "argumentierten" Prototype-Scoped-Bean hinzufügen und mithilfe des Arguments instanziieren.
Hier ist ein einfaches Beispiel für die Verwendung:
Dies wird natürlich Hallo-Zeichenfolge drucken, wenn usePrototype aufgerufen wird.
quelle
AKTUALISIERT pro Kommentar
Erstens bin ich mir nicht sicher, warum Sie sagen, dass "das nicht funktioniert" für etwas, das in Spring 3.x einwandfrei funktioniert. Ich vermute, dass irgendwo in Ihrer Konfiguration etwas nicht stimmt.
Das funktioniert:
-- Konfigurationsdatei:
- Auszuführende Testdatei:
Wenn Sie Spring 3.2.8 und Java 7 verwenden, erhalten Sie folgende Ausgabe:
Die 'Singleton'-Bohne wird also zweimal angefordert. Wie zu erwarten, erstellt Spring es jedoch nur einmal. Das zweite Mal sieht es, dass es diese Bean hat und gibt nur das vorhandene Objekt zurück. Der Konstruktor (@ Bean-Methode) wird kein zweites Mal aufgerufen. Wenn die 'Prototype'-Bean zweimal von demselben Kontextobjekt angefordert wird, sehen wir, dass sich die Referenz in der Ausgabe ändert UND dass der Konstruktor (@ Bean-Methode) zweimal aufgerufen wird.
Die Frage ist also, wie ein Singleton in einen Prototyp eingefügt werden kann. Die Konfigurationsklasse oben zeigt, wie das auch geht! Sie sollten alle diese Referenzen an den Konstruktor übergeben. Auf diese Weise kann die erstellte Klasse ein reines POJO sein und die enthaltenen Referenzobjekte unveränderlich machen, wie sie sein sollten. Der Transferservice könnte also ungefähr so aussehen:
Wenn Sie Unit Tests schreiben, werden Sie sehr froh sein, dass Sie die Klassen ohne @Autowired erstellt haben. Wenn Sie automatisch verdrahtete Komponenten benötigen, behalten Sie diese lokal in den Java-Konfigurationsdateien.
Dadurch wird die folgende Methode in der BeanFactory aufgerufen. Beachten Sie in der Beschreibung, wie dies für Ihren genauen Anwendungsfall vorgesehen ist.
quelle
Sie können einen ähnlichen Effekt erzielen, indem Sie eine innere Klasse verwenden :
quelle
Verwenden Sie in Ihrer Beans-XML-Datei das Attribut scope = "prototype".
quelle
Späte Antwort mit einem etwas anderen Ansatz. Dies ist eine Fortsetzung dieser jüngsten Frage , die sich auf diese Frage selbst bezieht.
Ja, wie gesagt, Sie können die Prototyp-Bean deklarieren, die einen Parameter in einer
@Configuration
Klasse akzeptiert , mit der bei jeder Injektion eine neue Bean erstellt werden kann.Das macht diese
@Configuration
Klasse zu einer Fabrik und um dieser Fabrik nicht zu viel Verantwortung zu übertragen, sollte dies keine anderen Bohnen einschließen.Sie können diese Konfigurations-Bean aber auch injizieren, um
Thing
s zu erstellen :Es ist sowohl typsicher als auch prägnant.
quelle
@Configuration
wenn sich dieser Mechanismus ändert.BeanFactory#getBean()
. In Bezug auf die Kopplung ist dies jedoch weitaus schlimmer, da dies eine Fabrik ist, die es ermöglicht, alle Beans der Anwendung abzurufen / zu instanziieren und nicht nur die, die die aktuelle Bean benötigt. Mit einer solchen Verwendung können Sie die Verantwortlichkeiten Ihrer Klasse sehr einfach mischen, da die Abhängigkeiten, die sie ziehen kann, unbegrenzt sind, was wirklich nicht empfohlen wird, aber ein Ausnahmefall.