Posso sostituire una definizione del bean Spring in fase di runtime?

Si consideri il seguente scenario. Ho un contesto di applicazione Spring con un bean le cui proprietà dovrebbero essere configurabili, pensate a DataSource o MailSender . La configurazione mutabile dell’applicazione è gestita da un bean separato, chiamiamola configuration .

Un amministratore può ora modificare i valori di configurazione, come l’indirizzo e-mail o l’URL del database, e desidero reinizializzare il bean configurato in fase di runtime.

Supponiamo che non sia sufficiente modificare semplicemente la proprietà del bean configurabile di cui sopra (ad esempio, creato da FactoryBean o dall’iniezione del costruttore), ma è necessario ricreare il bean stesso.

Qualche idea su come ottenere questo? Sarei felice di ricevere consigli su come organizzare l’intera configurazione. Niente è stato risolto 🙂

MODIFICARE

Per chiarire un po ‘le cose: non sto chiedendo come aggiornare la configurazione o come iniettare valori di configurazione statici. Proverò un esempio:

            

Quindi c’è un constructorInjectedBean beanInjectedBean che usa l’iniezione del costruttore. Immagina che la costruzione del bean sia molto costosa, quindi usare un scope prototipo o un proxy factory non è un’opzione, pensa DataSource .

Quello che voglio fare è che ogni volta che la configurazione viene aggiornata (tramite configurationService il bean constructorInjectedBean viene ricreato e reiniettato nel contesto dell’applicazione e nei bean dipendenti.

Possiamo tranquillamente supporre che constructorInjectedBean stia usando un’interfaccia in modo tale che la magia proxy sia davvero un’opzione.

Spero di aver reso la domanda un po ‘più chiara.

Posso pensare a un approccio “bean di supporto” (essenzialmente un decoratore), in cui il bean titolare delegato al detentore, ed è il bean titolare che viene iniettato come dipendenza in altri bean. Nessun altro ha un riferimento al titolare ma al titolare. Ora, quando la configurazione del bean del titolare viene modificata, ricrea il detentore con questa nuova configurazione e inizia a debind ad essa.

Ecco come l’ho fatto in passato: l’esecuzione di servizi che dipendono dalla configurazione che può essere modificata al volo implementa un’interfaccia per il ciclo di vita: 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 (o servizi) che possono modificare una parte della configurazione trasmessa a un argomento JMS che la configurazione è stata modificata (fornendo il nome dell’object di configurazione). Un bean message driven invoca quindi il contratto di interfaccia IRefreshable su tutti i bean che implementano IRefreshable.

La cosa bella con Spring è che puoi rilevare automaticamente qualsiasi servizio nel contesto dell’applicazione che deve essere aggiornato, rimuovendo la necessità di configurarli esplicitamente:

 public class MyCacheSynchService implements InitializingBean, ApplicationContextAware { public void afterPropertiesSet() throws Exception { Map refreshableServices = m_appCtx.getBeansOfType(IRefreshable.class); for (Map.Entry entry : refreshableServices.entrySet() ) { Object beanRef = entry.getValue(); if (beanRef instanceof IRefreshable) { m_refreshableServices.add((IRefreshable)beanRef); } } } } 

Questo approccio funziona particolarmente bene in un’applicazione cluster in cui uno dei molti server di applicazioni potrebbe modificare la configurazione, di cui tutti quindi devono essere a conoscenza. Se si desidera utilizzare JMX come meccanismo per l’triggerszione delle modifiche, il bean JMX può quindi trasmettere all’argomento JMS quando viene modificato uno qualsiasi degli attributi.

Dovresti dare un’occhiata a JMX . Spring offre anche supporto per questo.

  • Spring 2.0.x
  • Spring 2.5.x
  • Spring 3.0.x

Ulteriore risposta aggiornata per coprire il bean programmato

Un altro approccio supportato dalla molla 2.5.x + è quello del bean programmato. È ansible utilizzare una varietà di lingue per il proprio script: BeanShell è probabilmente il più intuitivo dal momento che ha la stessa syntax di Java, ma richiede alcune dipendenze esterne. Tuttavia, gli esempi sono in Groovy.

La sezione 24.3.1.2 della documentazione Spring spiega come configurare questo, ma qui ci sono alcuni estratti salienti che illustrano l’approccio che ho modificato per renderli più applicabili alla tua situazione:

    script-source="classpath:Messenger.groovy">       

Con lo script Groovy che assomiglia a questo:

 package org.example class GroovyMessenger implements Messenger { private String message = "anotherProperty"; public String getMessage() { return message; } public void setMessage(String message) { this.message = message } } 

Poiché l’amministratore di sistema desidera apportare delle modifiche, loro (o tu) possono modificare i contenuti dello script in modo appropriato. Lo script non fa parte dell’applicazione distribuita e può fare riferimento a un percorso file noto (o uno che è configurato tramite un PropertyPlaceholder configuratore standard durante l’avvio).

Sebbene l’esempio usi una class Groovy, potresti avere il codice execute della class che legge un semplice file delle proprietà. In questo modo, non modifichi mai direttamente lo script, basta toccarlo per modificare il timestamp. Tale azione triggers quindi il ricaricamento, che a sua volta triggers l’aggiornamento delle proprietà dal file delle proprietà (aggiornato), che aggiorna infine i valori all’interno del contesto Spring e in funzione.

La documentazione fa notare che questa tecnica non funziona per l’iniezione del costruttore, ma forse si può aggirare il problema.

Risposta aggiornata per coprire le modifiche alle proprietà dinamiche

Citando da questo articolo , che fornisce il codice sorgente completo , un approccio è:

 * a factory bean that detects file system changes * an observer pattern for Properties, so that file system changes can be propagated * a property placeholder configurer that remembers where which placeholders were used, and updates singleton beans' properties * a timer that triggers the regular check for changed files 

Il modello di osservatore è implementato dalle interfacce e classi ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent e ReloadablePropertiesBase. Nessuno di questi è particolarmente eccitante, solo la normale gestione degli ascoltatori. La class DelegatingProperties serve a scambiare in modo trasparente le proprietà correnti quando vengono aggiornate le proprietà. Aggiorniamo solo l’intera mappa delle proprietà in una volta, in modo che l’applicazione possa evitare stati intermedi inconsistenti (maggiori informazioni in seguito).

Ora è ansible scrivere ReloadablePropertiesFactoryBean per creare un’istanza ReloadableProperties (anziché un’istanza Properties, come fa PropertiesFactoryBean). Quando richiesto, l’RPFB controlla i tempi di modifica del file e, se necessario, aggiorna le sue proprietà ReloadableProperties. Questo innesca il meccanismo del modello osservatore.

Nel nostro caso, l’unico listener è ReloadingPropertyPlaceholderConfigurer. Si comporta come un tipico PropertyPlaceholderConfigurer di spring, tranne per il fatto che tiene traccia di tutti gli usi dei segnaposto. Ora, quando le proprietà vengono ricaricate, vengono trovati tutti gli usi di ciascuna proprietà modificata e le proprietà di tali bean singleton vengono nuovamente assegnate.

Risposta originale qui sotto che copre le modifiche alle proprietà statiche:

Sembra che tu voglia semplicemente iniettare proprietà esterne nel tuo contesto di spring. PropertyPlaceholderConfigurer è progettato per questo scopo:

       file:/some/admin/location/application.properties    

fai quindi riferimento alle proprietà esterne con i segnaposti della syntax Ant (che possono essere nidificati se vuoi partire da Spring 2.5.5 in poi)

     

Quindi assicurati che il file application.properties sia accessibile solo all’utente amministratore e all’utente che esegue l’applicazione.

Esempio application.properties:

password = Aardvark

O potresti usare l’approccio da questa domanda simile e quindi anche la mia soluzione :

L’approccio consiste nell’avere bean che sono configurati tramite file di proprietà e la soluzione è in entrambi

  • aggiornare l’intero applicationContext (automaticamente utilizzando un’attività pianificata o manualmente utilizzando JMX) quando le proprietà sono cambiate o
  • utilizzare un object provider di proprietà dedicato per accedere a tutte le proprietà. Questo fornitore di proprietà continuerà a controllare i file delle proprietà per la modifica. Per i bean in cui la ricerca di proprietà basata su prototipi è imansible, registrare un evento personalizzato che il provider di proprietà attiverà quando troverà un file di proprietà aggiornato. I tuoi bean con cicli di vita complicati dovranno ascoltare questo evento e aggiornarsi.

Questo non è qualcosa che ho provato, sto cercando di fornire dei suggerimenti.

Supponendo che il contesto dell’applicazione sia una sottoclass di AbstractRefreshableApplicationContext (ad esempio XmlWebApplicationContext, ClassPathXmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory () ti fornirà l’istanza di ConfigurableListableBeanFactory. Controlla se è un’istanza di BeanDefinitionRegistry. Se è così puoi chiamare il metodo ‘registerBeanDefinition’. Questo approccio sarà strettamente associato all’implementazione della spring,

Controlla il codice di AbstractRefreshableApplicationContext e DefaultListableBeanFactory (questa è l’implementazione che ottieni quando chiami ‘AbstractRefreshableApplicationContext getBeanFactory ()’)

È ansible creare un ambito personalizzato denominato “riconfigurabile” in ApplicationContext. Crea e memorizza nella cache le istanze di tutti i bean in questo ambito. In una modifica alla configurazione cancella la cache e ricrea i bean al primo accesso con la nuova configurazione. Affinché ciò funzioni, è necessario avvolgere tutte le istanze di bean riconfigurabili in un proxy con scope AOP e accedere ai valori di configurazione con Spring-EL: inserire una mappa denominata config in ApplicationContext e accedere alla configurazione come #{ config['key'] } .

Opzione 1 :

  1. Iniettare il bean configurable in DataSource o MailSender . Ottieni sempre i valori configurabili dal bean di configurazione da questi bean.
  2. All’interno del bean configurable eseguire un thread per leggere periodicamente le proprietà configurabili esternamente (file ecc.). In questo modo il bean configurable si aggiornerà automaticamente dopo che l’amministratore ha modificato le proprietà e quindi DataSource otterrà automaticamente i valori aggiornati.

Opzione 2 (male, penso, ma forse no – dipende dal caso d’uso):

  1. Crea sempre nuovi bean per i bean di tipo DataSource / MailSender – utilizzando l’ambito del prototype . Nell’iniziale del bean, leggere nuovamente le proprietà.

Opzione 3: Penso che il suggerimento @ mR_fr0g sull’utilizzo di JMX potrebbe non essere una ctriggers idea. Quello che potresti fare è:

  1. esporre il bean di configurazione come un MBean (leggi http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html )
  2. Chiedi all’amministratore di modificare le proprietà di configurazione sull’MBean (o fornire un’interfaccia nel bean per triggersre gli aggiornamenti delle proprietà dalla loro origine)
  3. Questo MBean (un nuovo pezzo di codice java che dovrai scrivere), DEVE mantenere i riferimenti dei bean (quelli che vuoi modificare / iniettare le proprietà modificate in). Questo dovrebbe essere semplice (tramite setter injection o runtime fetch di nomi / classi di bean)
    1. Quando la proprietà sull’MBean viene modificata (o triggersta), deve chiamare i setter appropriati sui rispettivi bean. In questo modo, il tuo codice precedente non cambia, puoi comunque gestire le modifiche alle proprietà di runtime.

HTH!

Si consiglia di dare un’occhiata a Spring Inspector, un componente plug-gable che fornisce accesso programmatico a qualsiasi applicazione basata su Spring in fase di esecuzione. È ansible utilizzare Javascript per modificare le configurazioni o gestire il comportamento dell’applicazione in fase di esecuzione.

Ecco la bella idea di scrivere il proprio PlaceholderConfigurer che tiene traccia dell’utilizzo delle proprietà e le modifica ogni volta che si verifica una modifica alla configurazione. Questo ha due svantaggi, però:

  1. Non funziona con l’iniezione del costruttore di valori di proprietà.
  2. È ansible ottenere condizioni di gara se il bean riconfigurato riceve una configurazione modificata mentre sta elaborando alcune cose.

La mia soluzione era copiare l’object originale. Pugno ho creato un’interfaccia

 /** * Allows updating data to some object. * Its an alternative to {@link Cloneable} when you cannot * replace the original pointer. Ex.: Beans * @param  Type of Object */ public interface Updateable { /** * Import data from another object * @param originalObject Object with the original data */ public void copyObject(T originalObject); } 

Per facilitare l’implementazione della funzione pugno, creare un costruttore con tutti i campi, quindi l’ IDE potrebbe aiutarmi un po ‘. Quindi è ansible creare un costruttore di copia che utilizza la stessa funzione Updateable#copyObject(T originalObject) . Puoi anche approfittare del codice del costruttore creato dall’IDE per creare la funzione da implementare:

 public class SettingsDTO implements Cloneable, Updateable { 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; } } 

L’ho usato in un controller per aggiornare le impostazioni correnti per l’app:

  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()); } } 

Quindi i currentSettingsData che hanno la configurazione dell’applicazione avranno i valori aggiornati, che si trovano in newSettingsData . Questo metodo consente di aggiornare qualsiasi bean senza elevata complessità.