Recharger les fichiers de propriétés au printemps

1. Vue d'ensemble

Dans ce tutoriel, nous allons montrer comment recharger les propriétés dans une application Spring.

2. Lire les propriétés au printemps

Nous avons différentes options pour accéder aux propriétés au printemps:

  1. Environnement - Nous pouvons injecter Environment , puis utiliser Environment # getProperty pour lire une propriété donnée. L'environnement contient différentes sources de propriétés telles que les propriétés système, les paramètres -D et application.properties (.yml) . En outre, des sources de propriété supplémentaires peuvent être ajoutées à l' environnement à l' aide de @PropertySource .
  2. Propriétés - Nous pouvons charger des fichiers de propriétés dans une instance de Propriétés , puis l'utiliser dans un bean en appelant properties.get («propriété»).
  3. @Value - Nous pouvons injecter une propriété spécifique dans un bean avec l' annotation @Value ($ {'property'}) .
  4. @ConfigurationProperties - nous pouvons utiliser @ConfigurationProperties pour charger des propriétés hiérarchiques dans un bean.

3. Rechargement des propriétés à partir d'un fichier externe

Pour modifier les propriétés d'un fichier pendant l'exécution, nous devons placer ce fichier quelque part en dehors du fichier jar. Ensuite, nous dirons à Spring où il se trouve avec la ligne de commandeparamètre –spring.config.location = fichier: // {chemin d'accès au fichier} . Ou, nous pouvons le mettre dans application.properties.

Dans les propriétés basées sur des fichiers, nous devrons choisir un moyen de recharger le fichier. Par exemple, nous pouvons développer un point de terminaison ou un planificateur pour lire le fichier et mettre à jour les propriétés.

Une bibliothèque pratique pour recharger le fichier est la configuration commune d'Apache . Nous pouvons utiliser PropertiesConfiguration avec différentes ReloadingStrategy .

Ajoutons commons-configuration à notre pom.xml :

 commons-configuration commons-configuration 1.10 

Ensuite, nous ajoutons une méthode pour créer un bean PropertiesConfiguration , que nous utiliserons plus tard:

@Bean @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false) public PropertiesConfiguration propertiesConfiguration( @Value("${spring.config.location}") String path) throws Exception { String filePath = new File(path.substring("file:".length())).getCanonicalPath(); PropertiesConfiguration configuration = new PropertiesConfiguration( new File(filePath)); configuration.setReloadingStrategy(new FileChangedReloadingStrategy()); return configuration; }

Dans le code ci-dessus, nous avons défini FileChangedReloadingStrategy comme stratégie de rechargement avec un délai d'actualisation par défaut. Cela signifie que PropertiesConfiguration vérifie la date de modification du fichier si sa dernière vérification était antérieure à 5000 ms .

Nous pouvons personnaliser le délai à l'aide de FileChangedReloadingStrategy # setRefreshDelay.

3.1. Rechargement des propriétés de l' environnement

Si nous voulons recharger les propriétés chargées via une instance d' environnement , nous devons étendre PropertySource , puis utiliser PropertiesConfiguration pour renvoyer de nouvelles valeurs à partir du fichier de propriétés externe .

Commençons par étendre le PropertySource :

public class ReloadablePropertySource extends PropertySource { PropertiesConfiguration propertiesConfiguration; public ReloadablePropertySource(String name, PropertiesConfiguration propertiesConfiguration) { super(name); this.propertiesConfiguration = propertiesConfiguration; } public ReloadablePropertySource(String name, String path) { super(StringUtils.hasText(name) ? path : name); try { this.propertiesConfiguration = new PropertiesConfiguration(path); this.propertiesConfiguration.setReloadingStrategy(new FileChangedReloadingStrategy()); } catch (Exception e) { throw new PropertiesException(e); } } @Override public Object getProperty(String s) { return propertiesConfiguration.getProperty(s); } }

Nous avons remplacé la méthode getProperty pour la déléguer à PropertiesConfiguration # getProperty. Par conséquent, il vérifiera les valeurs mises à jour par intervalles en fonction de notre délai d'actualisation.

Maintenant, nous allons ajouter notre ReloadablePropertySource aux sources de propriété de Environment :

@Configuration public class ReloadablePropertySourceConfig { private ConfigurableEnvironment env; public ReloadablePropertySourceConfig(@Autowired ConfigurableEnvironment env) { this.env = env; } @Bean @ConditionalOnProperty(name = "spring.config.location", matchIfMissing = false) public ReloadablePropertySource reloadablePropertySource(PropertiesConfiguration properties) { ReloadablePropertySource ret = new ReloadablePropertySource("dynamic", properties); MutablePropertySources sources = env.getPropertySources(); sources.addFirst(ret); return ret; } }

Nous avons ajouté la nouvelle source de propriété en tant que premier élément car nous voulons qu'elle remplace toute propriété existante avec la même clé.

Créons un bean pour lire une propriété depuis Environment :

@Component public class EnvironmentConfigBean { private Environment environment; public EnvironmentConfigBean(@Autowired Environment environment) { this.environment = environment; } public String getColor() { return environment.getProperty("application.theme.color"); } }

Si nous devons ajouter d'autres sources de propriétés externes rechargeables, nous devons d'abord implémenter notre PropertySourceFactory personnalisé :

public class ReloadablePropertySourceFactory extends DefaultPropertySourceFactory { @Override public PropertySource createPropertySource(String s, EncodedResource encodedResource) throws IOException { Resource internal = encodedResource.getResource(); if (internal instanceof FileSystemResource) return new ReloadablePropertySource(s, ((FileSystemResource) internal) .getPath()); if (internal instanceof FileUrlResource) return new ReloadablePropertySource(s, ((FileUrlResource) internal) .getURL() .getPath()); return super.createPropertySource(s, encodedResource); } }

Ensuite, nous pouvons annoter la classe d'un composant avec @PropertySource :

@PropertySource(value = "file:path-to-config", factory = ReloadablePropertySourceFactory.class)

3.2. Rechargement de l'instance de propriétés

L'environnement est un meilleur choix que Propriétés , en particulier lorsque nous avons besoin de recharger des propriétés à partir d'un fichier. Cependant, si nous en avons besoin, nous pouvons étendre java.util.Properties :

public class ReloadableProperties extends Properties { private PropertiesConfiguration propertiesConfiguration; public ReloadableProperties(PropertiesConfiguration propertiesConfiguration) throws IOException { super.load(new FileReader(propertiesConfiguration.getFile())); this.propertiesConfiguration = propertiesConfiguration; } @Override public String getProperty(String key) { String val = propertiesConfiguration.getString(key); super.setProperty(key, val); return val; } // other overrides }

Nous avons remplacé getProperty et ses surcharges, puis nous l'avons délégué à une instance de PropertiesConfiguration . Maintenant, nous pouvons créer un bean de cette classe et l'injecter dans nos composants.

3.3. Rechargement de Bean avec @ConfigurationProperties

Pour obtenir le même effet avec @ConfigurationProperties , nous aurions besoin de reconstruire l'instance.

Mais, Spring ne créera qu'une nouvelle instance de composants avec une portée de prototype ou de demande .

Ainsi, notre technique de rechargement de l'environnement fonctionnera également pour eux, mais pour les singletons, nous n'avons pas d'autre choix que d'implémenter un point de terminaison pour détruire et recréer le bean, ou de gérer le rechargement de propriété à l'intérieur du bean lui-même.

3.4. Recharger Bean avec @Value

L' annotation @Value présente les mêmes limitations que @ConfigurationProperties .

4. Rechargement des propriétés par actionneur et cloud

Spring Actuator provides different endpoints for health, metrics, and configs, but nothing for refreshing beans. Thus, we need Spring Cloud to add a /refresh endpoint to it. This endpoint reloads all property sources of Environment and then publishes an EnvironmentChangeEvent.

Spring Cloud also has introduced @RefreshScope, and we can use it for configuration classes or beans. As a result, the default scope will be refresh instead of singleton.

Using refresh scope, Spring will clear its internal cache of these components on an EnvironmentChangeEvent. Then, on the next access to the bean, a new instance is created.

Let's start by adding spring-boot-starter-actuator to our pom.xml:

 org.springframework.boot spring-boot-starter-actuator 

Then, let's also import spring-cloud-dependencies:

 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import Greenwich.SR1 

And then we add spring-cloud-starter:

 org.springframework.cloud spring-cloud-starter 

Finally, let's enable the refresh endpoint:

management.endpoints.web.exposure.include=refresh

When we use Spring Cloud, we can set up a Config Server to manage the properties, but we also can continue with our external files. Now, we can handle two other methods of reading properties: @Value and @ConfigurationProperties.

4.1. Refresh Beans with @ConfigurationProperties

Let's show how to use @ConfigurationProperties with @RefreshScope:

@Component @ConfigurationProperties(prefix = "application.theme") @RefreshScope public class ConfigurationPropertiesRefreshConfigBean { private String color; public void setColor(String color) { this.color = color; } //getter and other stuffs }

Our bean is reading “color” property from the root “application.theme” property. Note that we do need the setter method, per Spring's documentation.

After we change the value of “application.theme.color” in our external config file, we can call /refresh, so then, we can get the new value from the bean on next access.

4.2. Refresh Beans with @Value

Let's create our sample component:

@Component @RefreshScope public class ValueRefreshConfigBean { private String color; public ValueRefreshConfigBean(@Value("${application.theme.color}") String color) { this.color = color; } //put getter here }

The process of refreshing is the same as above.

However, it is necessary to note that /refresh won't work for beans with an explicit singleton scope.

5. Conclusion

In this tutorial, we've demonstrated how to reload properties with or without Spring Cloud features. Also, we've shown the pitfalls and exceptions of each of the techniques.

The complete code is available in our GitHub project.