Puis-je remplacer une définition de haricot Spring à l’exécution?

Considérez le scénario suivant. J’ai un contexte d’application Spring avec un bean dont les propriétés doivent être configurables, par exemple DataSource ou MailSender . La configuration de l’application mutable est gérée par un bean distinct, appelons-le configuration .

Un administrateur peut désormais modifier les valeurs de configuration, telles que l’adresse e-mail ou l’URL de la firebase database, et je souhaite réinitialiser le bean configuré à l’exécution.

Supposons que je ne puisse pas simplement modifier la propriété du bean configurable ci-dessus (par exemple, créée par FactoryBean ou l’injection du constructeur) mais que je dois recréer le bean lui-même.

Des reflections sur la façon d’y parvenir? Je serais ravi de recevoir des conseils sur la manière d’organiser l’ensemble de la configuration. Rien n’est fixe 🙂

MODIFIER

Pour clarifier les choses un peu: je ne demande pas comment mettre à jour la configuration ou comment injecter des valeurs de configuration statiques. Je vais essayer un exemple:

            

Donc, il y a un constructorInjectedBean beansInjectedBean qui utilise l’injection de constructeur. Imaginez que la construction du haricot soit très coûteuse, donc utiliser un prototype ou un proxy d’usine n’est pas une option, pense DataSource .

Ce que je veux faire, c’est que chaque fois que la configuration est mise à jour (via configurationService le constructorInjectedBean du beanInjectedBean est en train d’être recréé et réinjecté dans le contexte de l’application et les beans dépendants).

Nous pouvons supposer en toute sécurité que constructorInjectedBean utilise une interface afin que proxy magic soit une option.

J’espère avoir clarifié la question.

Je pense à une approche de type «haricot-titulaire» (essentiellement un décorateur), dans laquelle le détenteur délivre des doses à un détenteur, et c’est le haricot-support qui est injecté en tant que dépendance à d’autres haricots. Personne d’autre n’a de référence au détenteur mais au détenteur. Maintenant, lorsque la configuration du bean titulaire est modifiée, il recrée le fichier détenu avec cette nouvelle configuration et commence à lui déléguer.

Voici comment je l’ai fait par le passé: exécuter des services qui dépendent de la configuration qui peut être modifiée à la volée implémente une interface de cycle de vie: IRéférable:

 public interface IRefreshable { // Refresh the service having it apply its new values. public void refresh(Ssortingng filter); // The service must decide if it wants a cache refresh based on the refresh message filter. public boolean requiresRefresh(Ssortingng filter); } 

Les contrôleurs (ou services) qui peuvent modifier un élément de configuration diffusé sur un sujet JMS modifié par la configuration (en fournissant le nom de l’object de configuration). Un bean géré par message appelle alors le contrat d’interface IRsuppressable sur tous les beans qui implémentent IRefreshable.

La bonne chose avec Spring est que vous pouvez détecter automatiquement tout service dans votre contexte d’application qui doit être rafraîchi, ce qui supprime la nécessité de les configurer explicitement:

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

Cette approche fonctionne particulièrement bien dans une application en grappe où l’un des nombreux serveurs d’application peut modifier la configuration, dont tous doivent être au courant. Si vous souhaitez utiliser JMX comme mécanisme de déclenchement des modifications, votre bean JMX peut alors être diffusé sur la rubrique JMS lorsque l’un de ses atsortingbuts est modifié.

Vous devriez jeter un oeil à JMX . Spring fournit également un support pour cela.

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

Nouvelle réponse mise à jour pour couvrir le haricot scripté

Une autre approche soutenue par Spring 2.5.x + est celle du bean scripté. Vous pouvez utiliser différentes langues pour votre script – BeanShell est probablement le plus intuitif étant donné qu’il a la même syntaxe que Java, mais qu’il nécessite certaines dépendances externes. Cependant, les exemples sont dans Groovy.

La section 24.3.1.2 de la documentation de spring explique comment configurer cela, mais voici quelques extraits saillants illustrant l’approche que j’ai modifiée pour les rendre plus applicables à votre situation:

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

Avec le script Groovy ressemblant à ceci:

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

Lorsque l’administrateur système souhaite apporter des modifications, il (ou vous) peut modifier le contenu du script de manière appropriée. Le script ne fait pas partie de l’application déployée et peut faire référence à un emplacement de fichier connu (ou configuré via un PropertyPlaceholderConfigurer standard au démarrage).

Bien que l’exemple utilise une classe Groovy, la classe peut exécuter du code qui lit un fichier de propriétés simple. De cette manière, vous n’éditez jamais directement le script, touchez-le simplement pour modifier l’horodatage. Cette action déclenche ensuite le rechargement, qui à son tour déclenche le rafraîchissement des propriétés à partir du fichier de propriétés (mis à jour), qui met enfin à jour les valeurs dans le contexte Spring et c’est parti.

La documentation indique que cette technique ne fonctionne pas pour l’injection de constructeur, mais vous pouvez peut-être y remédier.

Réponse mise à jour pour couvrir les modifications de propriétés dynamics

Citant cet article , qui fournit le code source complet , une approche est la suivante:

 * 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 sortingggers the regular check for changed files 

Le modèle d’observateur est implémenté par les interfaces et les classes ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent et ReloadablePropertiesBase. Aucun d’entre eux n’est particulièrement excitant, juste la manipulation normale de l’auditeur. La classe DelegatingProperties sert à échanger de manière transparente les propriétés actuelles lorsque les propriétés sont mises à jour. Nous ne mettons à jour la totalité de la carte de propriété que de manière à ce que l’application puisse éviter les états intermédiaires incohérents (plus d’informations à ce sujet ultérieurement).

Maintenant, ReloadablePropertiesFactoryBean peut être écrit pour créer une instance ReloadableProperties (au lieu d’une instance Properties, comme le fait PropertiesFactoryBean). Lorsque vous y êtes invité, le RPFB vérifie les temps de modification des fichiers et, si nécessaire, met à jour ses propriétés ReloadableProperties. Cela déclenche la machinerie du modèle d’observateur.

Dans notre cas, le seul écouteur est le ReloadingPropertyPlaceholderConfigurer. Il se comporte comme un PropertyPlaceholderConfigurer à ressort standard, sauf qu’il suit tous les usages des espaces réservés. Maintenant, lorsque les propriétés sont rechargées, toutes les utilisations de chaque propriété modifiée sont trouvées et les propriétés de ces beans singleton sont atsortingbuées à nouveau.

Réponse originale ci-dessous couvrant les changements de propriétés statiques:

On dirait que vous voulez juste injecter des propriétés externes dans votre contexte Spring. Le PropertyPlaceholderConfigurer est conçu à cet effet:

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

vous faites ensuite référence aux propriétés externes avec les espaces réservés de syntaxe Ant (qui peuvent être nestedes si vous voulez à partir de Spring 2.5.5)

     

Vous vous assurez ensuite que le fichier application.properties est uniquement accessible à l’utilisateur admin et à l’utilisateur exécutant l’application.

Exemple d’application.properties:

mot de passe = Aardvark

Ou vous pouvez utiliser l’approche de cette question similaire et donc aussi ma solution :

L’approche consiste à avoir des beans configurés via des fichiers de propriétés et la solution consiste soit à:

  • actualiser l’intégralité de l’applicationContext (en utilisant automatiquement une tâche planifiée ou en utilisant manuellement JMX) lorsque les propriétés ont changé ou
  • utiliser un object de fournisseur de propriétés dédié pour accéder à toutes les propriétés. Ce fournisseur de propriétés continuera à vérifier les fichiers de propriétés pour les modifier. Pour les beans où la recherche de propriétés basée sur des prototypes est impossible, enregistrez un événement personnalisé que votre fournisseur de propriété déclenche lorsqu’il détecte un fichier de propriétés mis à jour. Vos beans avec des cycles de vie compliqués devront écouter cet événement et se rafraîchir.

Ce n’est pas quelque chose que j’ai essayé, j’essaye de fournir des indications.

En supposant que votre contexte d’application est une sous-classe de AbstractRefreshableApplicationContext (exemple XmlWebApplicationContext, ClassPathXmlApplicationContext). AbstractRefreshableApplicationContext.getBeanFactory () vous donnera une instance de ConfigurableListableBeanFactory. Vérifiez s’il s’agit d’une instance de BeanDefinitionRegistry. Si oui, vous pouvez appeler la méthode “registerBeanDefinition”. Cette approche sera étroitement associée à la mise en œuvre de Spring,

Vérifiez le code de AbstractRefreshableApplicationContext et DefaultListableBeanFactory (c’est l’implémentation que vous obtenez lorsque vous appelez ‘AbstractRefreshableApplicationContext getBeanFactory ()’)

Vous pouvez créer une étendue personnalisée appelée “reconfigurable” dans ApplicationContext. Il crée et met en cache des instances de tous les beans de cette étendue. Lors d’un changement de configuration, il efface le cache et recrée les beans lors du premier access avec la nouvelle configuration. Pour que cela fonctionne, vous devez envelopper toutes les instances de beans reconfigurables dans un proxy de scope AOP et accéder aux valeurs de configuration avec Spring-EL: placez une map appelée config dans ApplicationContext et accédez à la configuration comme #{ config['key'] } .

Option 1 :

  1. Injectez le bean configurable dans DataSource ou MailSender . Obtenez toujours les valeurs configurables du bean de configuration à partir de ces beans.
  2. Dans le bean configurable exécutez un thread pour lire les propriétés configurables en externe (fichier, etc.) périodiquement. De cette façon, le bean configurable se rafraîchira après que l’administrateur aura modifié les propriétés et le DataSource obtiendra automatiquement les valeurs mises à jour.

Option 2 (mauvais, je pense, mais peut-être pas – dépend du cas d’utilisation):

  1. Toujours créer de nouveaux beans pour les beans de type DataSource / MailSender – en utilisant la scope du prototype . Dans l’initiale du haricot, lisez à nouveau les propriétés.

Option 3: Je pense que la suggestion @ mR_fr0g sur l’utilisation de JMX n’est peut-être pas une mauvaise idée. Ce que vous pourriez faire, c’est:

  1. exposez votre bean de configuration en tant que MBean (lisez http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html )
  2. Demandez à votre administrateur de modifier les propriétés de configuration sur le MBean (ou fournissez une interface dans le bean pour déclencher les mises à jour de propriétés à partir de leur source)
  3. Ce MBean (un nouveau code Java à écrire), DOIT conserver les références des Beans (ceux dans lesquels vous voulez changer / injecter les propriétés modifiées). Cela devrait être simple (via l’injection de setter ou la récupération de noms / classes de beans à l’exécution)
    1. Lorsque la propriété du MBean est modifiée (ou déclenchée), elle doit appeler les parameters appropriés sur les beans respectifs. De cette façon, votre code hérité ne change pas, vous pouvez toujours gérer les modifications de propriété à l’exécution.

HTH!

Vous pouvez consulter le composant Spring Inspector, un composant plug-gable, qui fournit un access par programmation à toute application basée sur Spring au moment de l’exécution. Vous pouvez utiliser Javascript pour modifier les configurations ou gérer le comportement de l’application au moment de l’exécution.

Voici la bonne idée d’écrire votre propre PlaceholderConfigurer qui suit l’utilisation des propriétés et les modifie à chaque changement de configuration. Cela a deux inconvénients, cependant:

  1. Il ne fonctionne pas avec l’injection de constructeur des valeurs de propriété.
  2. Vous pouvez obtenir des conditions de concurrence si le bean reconfiguré reçoit une configuration modifiée pendant le traitement de certains éléments.

Ma solution consistait à copier l’object d’origine. Poing j’ai créé une interface

 /** * 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); } 

Pour faciliter l’implémentation de la fonction fist, créez un constructeur avec tous les champs afin que l’ EDI puisse m’aider un peu. Ensuite, vous pouvez créer un constructeur de copie qui utilise la même fonction Updateable#copyObject(T originalObject) . Vous pouvez également profiter du code du constructeur créé par l’ EDI pour créer la fonction à implémenter:

 public class SettingsDTO implements Cloneable, Updateable { private static final Logger LOG = LoggerFactory.getLogger(SettingsDTO.class); @Size(min = 3, max = 30) private Ssortingng id; @Size(min = 3, max = 30) @NotNull private Ssortingng name; @Size(min = 3, max = 100) @NotNull private Ssortingng description; @Max(100) @Min(5) @NotNull private Integer pageSize; @NotNull private Ssortingng dateFormat; public SettingsDTO() { } public SettingsDTO(Ssortingng id, Ssortingng name, Ssortingng description, Integer pageSize, Ssortingng 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; } } 

Je l’ai utilisé dans un contrôleur pour mettre à jour les parameters actuels de l’application:

  if (bindingResult.hasErrors()) { model.addAtsortingbute("settingsData", newSettingsData); model.addAtsortingbute(Templates.MSG_ERROR, "The entered data has errors"); } else { synchronized (settingsData) { currentSettingData.copyObject(newSettingsData); redirectAtsortingbutes.addFlashAtsortingbute(Templates.MSG_SUCCESS, "The system configuration has been updated successfully"); return Ssortingng.format("redirect:/%s", getDao().getPath()); } } 

Donc, le currentSettingsData qui a la configuration de l’application aura les valeurs mises à jour, situées dans newSettingsData . Ces méthodes permettent de mettre à jour n’importe quel bean sans grande complexité.