Stellen Sie sich das folgende Szenario vor. Ich habe einen Spring - Anwendungskontext mit einer Bohne , deren Eigenschaften sollte konfigurierbar sein, denken DataSource
oder MailSender
. Die veränderbare Anwendungskonfiguration wird von einer separaten Bean verwaltet. Nennen wir es configuration
.
Ein Administrator kann jetzt die Konfigurationswerte wie E-Mail-Adresse oder Datenbank-URL ändern, und ich möchte die konfigurierte Bean zur Laufzeit neu initialisieren.
Angenommen, ich kann nicht einfach die Eigenschaft der oben konfigurierbaren Bean ändern (z. B. erstellt durch FactoryBean
oder Konstruktorinjektion), sondern muss die Bean selbst neu erstellen.
Irgendwelche Gedanken darüber, wie dies erreicht werden kann? Ich würde mich freuen, Ratschläge zu erhalten, wie auch die gesamte Konfigurationssache organisiert werden kann. Nichts ist behoben. :-)
BEARBEITEN
Um die Dinge ein wenig zu klären: Ich frage nicht, wie die Konfiguration aktualisiert oder statische Konfigurationswerte eingefügt werden sollen. Ich werde ein Beispiel versuchen:
<beans>
<util:map id="configuration">
<!-- initial configuration -->
</util:map>
<bean id="constructorInjectedBean" class="Foo">
<constructor-arg value="#{configuration['foobar']}" />
</bean>
<bean id="configurationService" class="ConfigurationService">
<property name="configuration" ref="configuration" />
</bean>
</beans>
Es gibt also eine Bohne constructorInjectedBean
, die Konstruktorinjektion verwendet. Stellen Sie sich vor, die Konstruktion der Bohne ist sehr teuer, daher ist die Verwendung eines Prototyp-Oszilloskops oder eines Factory-Proxys keine Option DataSource
.
Was ich tun möchte, ist, dass jedes Mal, wenn die Konfiguration aktualisiert wird (über configurationService
die Bean constructorInjectedBean
wird sie neu erstellt und erneut in den Anwendungskontext und die abhängigen Beans eingefügt).
Wir können davon ausgehen, dass constructorInjectedBean
eine Schnittstelle verwendet wird, sodass Proxy-Magie in der Tat eine Option ist.
Ich hoffe, die Frage etwas klarer gestellt zu haben.
configuration
Bean muss also zur Laufzeit aktualisiert werden - oder jedes Mal, wenn der Administrator die Werte ändert? Ich das deine Frage? Oder möchten Sie, dass dieDataSource
/MailSender
Beans zur Laufzeit die aktualisierte Konfiguration verwenden? Oder ist es beides?Antworten:
Ich kann mir einen "Holder Bean" -Ansatz vorstellen (im Wesentlichen ein Dekorateur), bei dem die Holder Bean an Holdee delegiert und die Holder Bean als Abhängigkeit in andere Bohnen injiziert wird. Niemand außer dem Inhaber hat einen Hinweis auf den Inhaber. Wenn nun die Konfiguration der Holder-Bean geändert wird, wird der Holdee mit dieser neuen Konfiguration neu erstellt und an sie delegiert.
quelle
So habe ich es in der Vergangenheit gemacht: Ausführen von Diensten, die von der Konfiguration abhängen und im laufenden Betrieb geändert werden können, Implementieren einer Lebenszyklusschnittstelle: IRefreshable:
public interface IRefreshable { // Refresh the service having it apply its new values. public void refresh(String filter); // The service must decide if it wants a cache refresh based on the refresh message filter. public boolean requiresRefresh(String filter); }
Controller (oder Dienste), die eine Konfigurationsübertragung an ein JMS-Thema ändern können, das von der Konfiguration geändert wurde (unter Angabe des Namens des Konfigurationsobjekts). Eine nachrichtengesteuerte Bean ruft dann den IRefreshable-Schnittstellenvertrag für alle Beans auf, die IRefreshable implementieren.
Das Schöne an spring ist, dass Sie automatisch jeden Dienst in Ihrem Anwendungskontext erkennen können, der aktualisiert werden muss, sodass sie nicht mehr explizit konfiguriert werden müssen:
public class MyCacheSynchService implements InitializingBean, ApplicationContextAware { public void afterPropertiesSet() throws Exception { Map<String, ?> refreshableServices = m_appCtx.getBeansOfType(IRefreshable.class); for (Map.Entry<String, ?> entry : refreshableServices.entrySet() ) { Object beanRef = entry.getValue(); if (beanRef instanceof IRefreshable) { m_refreshableServices.add((IRefreshable)beanRef); } } } }
Dieser Ansatz funktioniert besonders gut in einer Clusteranwendung, in der einer von vielen App-Servern möglicherweise die Konfiguration ändert, die alle dann kennen müssen. Wenn Sie JMX als Mechanismus zum Auslösen der Änderungen verwenden möchten, kann Ihre JMX-Bean dann an das JMS-Thema senden, wenn eines ihrer Attribute geändert wird.
quelle
Sie sollten sich JMX ansehen . Auch hierfür unterstützt der Frühling.
quelle
@ManagedResource
und die Bean-Methoden mit@ManagedAttribute
. Dann können Sie mit einem JMX-Client eine Verbindung zur laufenden Instanz herstellen (ich verwende siejvisualvm
mit den entsprechenden Plugins).Weitere aktualisierte Antwort auf geskriptete Bean
Ein weiterer Ansatz, der von Spring 2.5.x + unterstützt wird, ist der der Scripted Bean. Sie können eine Vielzahl von Sprachen für Ihr Skript verwenden - BeanShell ist wahrscheinlich die intuitivste, da es dieselbe Syntax wie Java hat, jedoch einige externe Abhängigkeiten erfordert. Die Beispiele sind jedoch in Groovy.
In Abschnitt 24.3.1.2 der Frühjahrsdokumentation wird beschrieben, wie dies konfiguriert wird. Hier sind jedoch einige wichtige Auszüge, die den Ansatz veranschaulichen, den ich bearbeitet habe, um sie für Ihre Situation besser anwendbar zu machen:
<beans> <!-- This bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute --> <lang:groovy id="messenger" refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks --> script-source="classpath:Messenger.groovy"> <lang:property name="message" value="defaultMessage" /> </lang:groovy> <bean id="service" class="org.example.DefaultService"> <property name="messenger" ref="messenger" /> </bean> </beans>
Das Groovy-Skript sieht folgendermaßen aus:
package org.example class GroovyMessenger implements Messenger { private String message = "anotherProperty"; public String getMessage() { return message; } public void setMessage(String message) { this.message = message } }
Da der Systemadministrator Änderungen vornehmen möchte, können sie (oder Sie) den Inhalt des Skripts entsprechend bearbeiten. Das Skript ist nicht Teil der bereitgestellten Anwendung und kann auf einen bekannten Speicherort verweisen (oder auf einen, der beim Start über einen Standard-PropertyPlaceholderConfigurer konfiguriert wird).
Obwohl im Beispiel eine Groovy-Klasse verwendet wird, kann die Klasse Code ausführen, der eine einfache Eigenschaftendatei liest. Auf diese Weise bearbeiten Sie das Skript nie direkt. Berühren Sie es einfach, um den Zeitstempel zu ändern. Diese Aktion löst dann das Neuladen aus, was wiederum die Aktualisierung von Eigenschaften aus der (aktualisierten) Eigenschaftendatei auslöst, wodurch schließlich die Werte im Spring-Kontext aktualisiert werden und los geht's.
In der Dokumentation wird darauf hingewiesen, dass diese Technik für die Konstruktorinjektion nicht funktioniert, aber vielleicht können Sie das umgehen.
Die Antwort wurde aktualisiert, um dynamische Eigenschaftsänderungen abzudecken
Ein Ansatz aus diesem Artikel , der den vollständigen Quellcode enthält , lautet:
Die ursprüngliche Antwort unten bezieht sich auf statische Eigenschaftsänderungen:
Klingt so, als ob Sie nur externe Eigenschaften in Ihren Spring-Kontext einfügen möchten. Das
PropertyPlaceholderConfigurer
ist für diesen Zweck ausgelegt:<!-- Property configuration (if required) --> <bean id="serverProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <!-- Identical properties in later files overwrite earlier ones in this list --> <value>file:/some/admin/location/application.properties</value> </list> </property> </bean>
Anschließend verweisen Sie mit Platzhaltern für die Ant-Syntax auf die externen Eigenschaften (die verschachtelt werden können, wenn Sie dies ab Spring 2.5.5 möchten).
<bean id="example" class="org.example.DataSource"> <property name="password" value="${password}"/> </bean>
Anschließend stellen Sie sicher, dass auf die Datei application.properties nur der Administrator und der Benutzer zugreifen können, der die Anwendung ausführt.
Beispiel application.properties:
Passwort = Erdferkel
quelle
DataSource
handelt, die mit a erstellt wurdeFactoryBean
. Der erwähnte Ansatz würde nur die Werte der Factory Bean aktualisieren, was überhaupt keine Hilfe ist. :-(Oder Sie könnten den Ansatz aus dieser ähnlichen Frage und damit auch meine Lösung verwenden :
Der Ansatz besteht darin, Beans zu haben, die über Eigenschaftendateien konfiguriert werden, und die Lösung besteht darin, entweder
quelle
Dies habe ich nicht versucht, ich versuche, Hinweise zu geben.
Angenommen, Ihr Anwendungskontext ist eine Unterklasse von AbstractRefreshableApplicationContext (Beispiel XmlWebApplicationContext, ClassPathXmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory () gibt Ihnen eine Instanz von ConfigurableListableBeanFactory. Überprüfen Sie, ob es sich um eine Instanz von BeanDefinitionRegistry handelt. In diesem Fall können Sie die Methode 'registerBeanDefinition' aufrufen. Dieser Ansatz wird eng mit der Implementierung von Spring verbunden sein.
Überprüfen Sie den Code von AbstractRefreshableApplicationContext und DefaultListableBeanFactory (dies ist die Implementierung, die Sie erhalten, wenn Sie 'AbstractRefreshableApplicationContext getBeanFactory ()' aufrufen.)
quelle
registerBeanDefinition()
istorg.springframework.beans.factory.support.BeanDefinitionRegistry
übrigens. Ich werde das untersuchen, danke.Sie können einen benutzerdefinierten Bereich mit dem Namen "rekonfigurierbar" im ApplicationContext erstellen. Es werden Instanzen aller Beans in diesem Bereich erstellt und zwischengespeichert. Bei einer Konfigurationsänderung wird der Cache geleert und die Beans werden beim ersten Zugriff mit der neuen Konfiguration neu erstellt. Damit dies funktioniert, müssen Sie alle Instanzen rekonfigurierbarer Beans in einen Proxy mit AOP-Gültigkeitsbereich einbinden und mit Spring-EL auf die Konfigurationswerte zugreifen: Fügen Sie eine aufgerufene Map
config
in den ApplicationContext ein und greifen Sie wie folgt auf die Konfiguration zu#{ config['key'] }
.quelle
Option 1 :
configurable
Bohne in dasDataSource
oderMailSender
. Rufen Sie die konfigurierbaren Werte immer aus der Konfigurations-Bean in diesen Beans ab.configurable
Führen Sie innerhalb der Bean einen Thread aus, um die extern konfigurierbaren Eigenschaften (Datei usw.) regelmäßig zu lesen. Auf diese Weise aktualisiert sich dieconfigurable
Bean selbst, nachdem der Administrator die Eigenschaften geändert hat, undDataSource
erhält die aktualisierten Werte automatisch.Option 2 (schlecht, denke ich, aber vielleicht nicht - hängt vom Anwendungsfall ab):
DataSource
/MailSender
- mit demprototype
Gültigkeitsbereich. Lesen Sie im Init der Bean die Eigenschaften erneut.Option 3: Ich denke, @ mR_fr0g Vorschlag zur Verwendung von JMX ist möglicherweise keine schlechte Idee. Was Sie tun könnten, ist:
HTH!
quelle
Vielleicht möchten Sie einen Blick auf den Spring Inspector werfen, eine steckbare Komponente, die zur Laufzeit programmgesteuerten Zugriff auf jede Spring-basierte Anwendung bietet. Mit Javascript können Sie Konfigurationen ändern oder das Anwendungsverhalten zur Laufzeit verwalten.
quelle
Hier ist die gute Idee, einen eigenen PlaceholderConfigurer zu schreiben, der die Verwendung von Eigenschaften verfolgt und diese bei jeder Konfigurationsänderung ändert. Dies hat jedoch zwei Nachteile:
quelle
Meine Lösung bestand darin, das ursprüngliche Objekt zu kopieren. Faust Ich habe eine Schnittstelle erstellt
/** * Allows updating data to some object. * Its an alternative to {@link Cloneable} when you cannot * replace the original pointer. Ex.: Beans * @param <T> Type of Object */ public interface Updateable<T> { /** * Import data from another object * @param originalObject Object with the original data */ public void copyObject(T originalObject); }
Um die Implementierung der Funktion faust zu vereinfachen, erstellen Sie einen Konstruktor mit allen Feldern, damit die IDE mir ein bisschen helfen kann. Dann können Sie einen Kopierkonstruktor erstellen, der dieselbe Funktion verwendet
Updateable#copyObject(T originalObject)
. Sie können auch vom Code des von der IDE erstellten Konstruktors profitieren , um die zu implementierende Funktion zu erstellen:public class SettingsDTO implements Cloneable, Updateable<SettingsDTO> { private static final Logger LOG = LoggerFactory.getLogger(SettingsDTO.class); @Size(min = 3, max = 30) private String id; @Size(min = 3, max = 30) @NotNull private String name; @Size(min = 3, max = 100) @NotNull private String description; @Max(100) @Min(5) @NotNull private Integer pageSize; @NotNull private String dateFormat; public SettingsDTO() { } public SettingsDTO(String id, String name, String description, Integer pageSize, String dateFormat) { this.id = id; this.name = name; this.description = description; this.pageSize = pageSize; this.dateFormat = dateFormat; } public SettingsDTO(SettingsDTO original) { copyObject(original); } @Override public void copyObject(SettingsDTO originalObject) { this.id = originalObject.id; this.name = originalObject.name; this.description = originalObject.description; this.pageSize = originalObject.pageSize; this.dateFormat = originalObject.dateFormat; } }
Ich habe es in einem Controller verwendet, um die aktuellen Einstellungen für die App zu aktualisieren:
if (bindingResult.hasErrors()) { model.addAttribute("settingsData", newSettingsData); model.addAttribute(Templates.MSG_ERROR, "The entered data has errors"); } else { synchronized (settingsData) { currentSettingData.copyObject(newSettingsData); redirectAttributes.addFlashAttribute(Templates.MSG_SUCCESS, "The system configuration has been updated successfully"); return String.format("redirect:/%s", getDao().getPath()); } }
So ist die ,
currentSettingsData
die die Konfiguration der Anwendung hat im Begriff , die aktualisierten Werte haben, innewSettingsData
. Diese Methode ermöglicht die Aktualisierung jeder Bean ohne hohe Komplexität.quelle