Dynamisch festlegen, welche Bean im Frühling autowire werden soll (mit Qualifiern)

8

Ich habe eine Java EE + Spring-App, die Annotationen gegenüber der XML-Konfiguration bevorzugt. Die Beans haben immer einen Prototypbereich.

Ich habe jetzt in meiner App Geschäftsregeln, die von dem Land abhängen, aus dem die Benutzeranfrage stammt. Also hätte ich so etwas (beachte, dass dieses Beispiel stark vereinfacht wurde):

%Vor%

Ich suchte nach einer Möglichkeit, den automatischen Verdrahtungsmechanismus automatisch die richtige Bohne (in diesem Beispiel entweder USA oder Kanada) basierend auf dem Land der aktuellen Anfrage zu injizieren. Das Land würde in einer ThreadLocal-Variablen gespeichert und bei jeder Anfrage geändert. Es würde auch eine globale Klasse geben, für alle Länder, die keine eigenen Regeln haben.

Ich würde mir vorstellen, dass ich die Art und Weise anpassen müsste, in der Spring entscheidet, wie die Objekte erstellt werden, die injiziert werden. Die einzige Möglichkeit, dies zu tun, war die Verwendung von FactoryBean, aber das war nicht ganz das, was ich mir erhofft hatte (nicht generisch genug). Ich hatte gehofft, so etwas zu tun:

  1. Bevor Spring ein Objekt instanziiert, müsste mein eigener benutzerdefinierter Code aufgerufen werden.
  2. Wenn ich feststelle, dass die angeforderte Schnittstelle mehr als eine Implementierung hat, würde ich in meiner ThreadLocal-Variablen das richtige Land nachschlagen und würde dynamisch den entsprechenden Qualifier zur Auto-Wire-Anforderung hinzufügen.
  3. Danach würde Spring all seine übliche Magie tun. Wenn ein Qualifier hinzugefügt würde, müsste dies berücksichtigt werden; Wenn nicht, würde der Fluss wie gewohnt ablaufen.

Bin ich auf dem richtigen Weg? Irgendwelche Ideen für mich dazu?

Danke.

    
zleand 11.10.2011, 01:23
quelle

2 Antworten

4

Erstellen Sie Ihre eigene Annotation, die zur Verzierung von Instanzvariablen oder Setter-Methoden verwendet wird, und anschließend einen Postprozessor, der die Annotation verarbeitet und einen generischen Proxy einfügt, der die korrekte Implementierung zur Laufzeit auflöst und den Aufruf an ihn delegiert.

%Vor%

Hier ist der Algorithmus für die Methode postProcessBeforeInitialization(bean, beanName) in Ihrem Bean-Postprozessor:

  1. Überprüfen Sie die Bean-Klasse, um Instanzvariablen oder Setter-Methoden zu finden, die mit @LocalizedResource annotiert sind. Speichern Sie das Ergebnis in einem Cache (nur eine Map), der vom Klassennamen indiziert wird. Zu diesem Zweck können Sie Spring's InjectionMetadata verwenden. Sie können nach Beispielen suchen, wie es funktioniert, indem Sie Referenzen auf diese Klasse im Springcode suchen.
  2. Wenn ein solches Feld oder eine Methode für die Bean existiert, erstellen Sie einen Proxy mit dem unten beschriebenen InvocationHandler und übergeben Sie ihm die aktuelle BeanFactory (der Bean-Postprozessor muss ApplicationContextAware sein). Geben Sie diesen Proxy in die Instanzvariable ein oder rufen Sie die Setter-Methode mit der Proxy-Instanz auf.

Hier ist der InvocationHandler für den Proxy, der zum Erstellen lokalisierter Ressourcen verwendet wird.

%Vor%

Sie müssen möglicherweise mehr Steuerelemente für den Bean-Typ festlegen oder den angeforderten Bean-Typ im InvocationHandler hinzufügen.

Als nächstes müssen Sie Implementierungen einer gegebenen Schnittstelle, die lokal abhängig sind, automatisch erkennen und sie mit dem Qualifikationsmerkmal registrieren, das dem Gebietsschema entspricht. Sie können zu diesem Zweck ein BeanDefinitionRegistryPostProcessor oder BeanFactoryPostProcessor implementieren, um der Registrierung ein neues BeanDefinition s hinzuzufügen, mit einem geeigneten Qualifikationsmerkmal, eines für jede Implementierung von länderspezifischen Schnittstellen. Sie können das Gebietsschema einer Implementierung anhand von Namenskonventionen erraten: Wenn eine länderspezifische Schnittstelle TransactionRules genannt wird, können Implementierungen im selben Paket den Namen Transac- tionRules_ISOCODE tragen.

Wenn Sie sich eine solche Namenskonvention nicht leisten können, müssen Sie eine Art Klassenpfad-Überprüfung durchführen + eine Möglichkeit, das Gebietsschema einer bestimmten Implementierung zu erraten (möglicherweise eine Anmerkung zu den Implementierungsklassen). Classpath-Scanning ist möglich, aber ziemlich komplex und langsam, also versuchen Sie es zu vermeiden.

Hier ist eine Zusammenfassung dessen, was passiert:

  1. Beim Start der Anwendung werden Implementierungen von TransactionRules erkannt und Bean-Definitionen für jede von ihnen erstellt, wobei ein Qualifikationsmerkmal dem Gebietsschema jeder Implementierung entspricht. Der Bean-Name für diese Beans ist nicht relevant, da die Suche basierend auf Typ und Qualifier durchgeführt wird.
  2. Legen Sie während der Ausführung das aktuelle Gebietsschema in einer threadlokalen Variablen fest
  3. Suchen Sie die benötigte Bean (zB TransactionService). Der Postprozessor fügt für jede @LocalizedResource-Instanz ein Feld oder eine Setter-Methode ein Proxy ein.
  4. Beim Aufrufen einer Methode auf TransactionService, die in einer Methode von TransactionRules endete, wechselt der an den Proxy gebundene Aufrufhandler basierend auf dem in der threadlokalen Variablen gespeicherten Wert zur richtigen Implementierung und delegiert dann den Aufruf an diese Implementierung.

Nicht wirklich trivial, aber es funktioniert. So wird @PersistenceContext von Spring verarbeitet, mit Ausnahme der Implementierungssuche, die ein zusätzliches Feature Ihres Anwendungsfalls darstellt.

    
Gaetan 22.07.2013 11:56
quelle
0

Sie können eine Konfigurationsklasse angeben, die basierend auf dem ThreadLocal-Wert die richtige Bean zurückgibt. Dies setzt voraus, dass Sie Spring 3 verwenden. Ich habe einen kleinen Test durchgeführt, um sicherzustellen, dass die Provider-Methode bei jeder Anfrage aufgerufen wurde. Hier ist was ich getan habe.

%Vor%

Und referenzierte den Wert in meinem Controller wie folgt.

%Vor%

In Ihrer Implementierung des Providers können Sie das ThreadLocal für das Gebietsschema überprüfen und das korrekte TransactionRules-Objekt oder etwas ähnliches zurückgeben. Das ScopedProxy-Zeug ist, weil ich in einen Controller injiziert habe, der Singleton-Bereich ist, während der Wert Request-Bereich ist.

    
digitaljoel 11.10.2011 02:00
quelle

Tags und Links