Ricaricamento dei file delle proprietà in primavera

1. Panoramica

In questo tutorial, mostreremo come ricaricare le proprietà in un'applicazione Spring.

2. Proprietà di lettura in primavera

Abbiamo diverse opzioni per accedere alle proprietà in primavera:

  1. Ambiente : possiamo inserire Environment e quindi utilizzare Environment # getProperty per leggere una determinata proprietà. L'ambiente contiene diverse sorgenti di proprietà come proprietà di sistema, parametri -D e application.properties (.yml) . Inoltre, è possibile aggiungere origini di proprietà extra all'ambiente utilizzando @PropertySource .
  2. Properties - Possiamo caricare i file delle proprietà in un'istanza Properties , quindi utilizzarli in un bean chiamando properties.get ("proprietà").
  3. @Value - Possiamo iniettare una proprietà specifica in un bean con l' annotazione @Value ($ {'property'}) .
  4. @ConfigurationProperties - possiamo usare @ConfigurationProperties per caricare le proprietà gerarchiche in un bean.

3. Ricaricamento delle proprietà da un file esterno

Per modificare le proprietà in un file durante il runtime, dovremmo posizionare quel file da qualche parte fuori dal jar. Quindi, diremo a Spring dove si trova con la riga di comandoparametro –spring.config.location = file: // {percorso del file} . Oppure possiamo inserirlo in application.properties.

Nelle proprietà basate su file, dovremo scegliere un modo per ricaricare il file. Ad esempio, possiamo sviluppare un endpoint o uno scheduler per leggere il file e aggiornare le proprietà.

Una comoda libreria per ricaricare il file è la configurazione comune di Apache . Possiamo usare PropertiesConfiguration con differenti ReloadingStrategy .

Aggiungiamo la configurazione dei comuni al nostro pom.xml :

 commons-configuration commons-configuration 1.10 

Quindi, aggiungiamo un metodo per creare un bean PropertiesConfiguration , che useremo in seguito:

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

Nel codice precedente, abbiamo impostato FileChangedReloadingStrategy come strategia di ricaricamento con un ritardo di aggiornamento predefinito. Ciò significa che PropertiesConfiguration verifica la data di modifica del file se l'ultimo controllo è stato effettuato prima di 5000 ms fa .

Possiamo personalizzare il ritardo utilizzando FileChangedReloadingStrategy # setRefreshDelay.

3.1. Ricaricamento delle proprietà dell'ambiente

Se vogliamo ricaricare le proprietà caricate tramite un'istanza di Environment , dobbiamo estendere PropertySource e quindi utilizzare PropertiesConfiguration per restituire nuovi valori dal file di proprietà esterno .

Cominciamo con l'estensione di 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); } }

Abbiamo sovrascritto il metodo getProperty per delegarlo a PropertiesConfiguration # getProperty. Quindi, controllerà i valori aggiornati a intervalli in base al nostro ritardo di aggiornamento.

Ora, aggiungeremo il nostro ReloadablePropertySource alle origini delle proprietà di 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; } }

Abbiamo aggiunto la nuova origine della proprietà come primo elemento perché vogliamo che sovrascriva qualsiasi proprietà esistente con la stessa chiave.

Creiamo un bean per leggere una proprietà da 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"); } }

Se dobbiamo aggiungere altre origini di proprietà esterne ricaricabili, dobbiamo prima implementare la nostra PropertySourceFactory personalizzata :

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

Quindi possiamo annotare la classe di un componente con @PropertySource :

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

3.2. Ricaricamento dell'istanza delle proprietà

L'ambiente è una scelta migliore rispetto a Proprietà , soprattutto quando è necessario ricaricare le proprietà da un file. Tuttavia, se ne abbiamo bisogno, possiamo estendere 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 }

Abbiamo sovrascritto getProperty e i suoi overload, quindi lo abbiamo delegato a un'istanza PropertiesConfiguration . Ora possiamo creare un bean di questa classe e iniettarlo nei nostri componenti.

3.3. Ricaricamento di Bean con @ConfigurationProperties

Per ottenere lo stesso effetto con @ConfigurationProperties , avremmo bisogno di ricostruire l'istanza.

Tuttavia, Spring creerà solo una nuova istanza di componenti con prototipo o ambito di richiesta .

Quindi, la nostra tecnica per ricaricare l'ambiente funzionerà anche per loro, ma per i single non abbiamo altra scelta se non implementare un endpoint per distruggere e ricreare il bean, o per gestire il ricaricamento della proprietà all'interno del bean stesso.

3.4. Ricaricamento di Bean con @Value

L' annotazione @Value presenta le stesse limitazioni di @ConfigurationProperties .

4. Ricaricamento delle proprietà da Actuator e Cloud

Spring Actuator fornisce diversi endpoint per salute, metriche e configurazioni, ma niente per aggiornare i bean. Quindi, abbiamo bisogno di Spring Cloud per aggiungere un endpoint / refresh ad esso. Questo endpoint ricarica tutte le origini delle proprietà di Environment e quindi pubblica un EnvironmentChangeEvent .

Spring Cloud ha anche introdotto @RefreshScope e possiamo usarlo per classi di configurazione o bean. Di conseguenza, l'ambito predefinito verrà aggiornato anziché singleton .

Utilizzando l' ambito di aggiornamento , Spring cancellerà la cache interna di questi componenti su un EnvironmentChangeEvent . Quindi, al successivo accesso al bean, viene creata una nuova istanza.

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.