Neuladen von Eigenschaftendateien im Frühjahr

1. Übersicht

In diesem Tutorial zeigen wir Ihnen, wie Sie Eigenschaften in einer Spring-Anwendung neu laden.

2. Eigenschaften im Frühjahr lesen

Wir haben verschiedene Möglichkeiten, um im Frühjahr auf Eigenschaften zuzugreifen:

  1. Umgebung - Wir können Umgebung injizieren und dann Umgebung # getProperty verwenden , um eine bestimmte Eigenschaft zu lesen. Die Umgebung enthält verschiedene Eigenschaftsquellen wie Systemeigenschaften, -D- Parameter und application.properties (.yml) . Außerdem können der Umgebung mithilfe von @PropertySource zusätzliche Eigenschaftsquellen hinzugefügt werden .
  2. Eigenschaften - Wir können Eigenschaftendateien in eine Eigenschafteninstanz laden und sie dann in einer Bean verwenden, indem wir properties.get ("Eigenschaft") aufrufen .
  3. @Value - Wir können eine bestimmte Eigenschaft in eine Bean mit der Annotation @Value ($ {'property'}) einfügen.
  4. @ConfigurationProperties - Mit @ConfigurationProperties können hierarchische Eigenschaften in eine Bean geladen werden.

3. Eigenschaften aus externer Datei neu laden

Um die Eigenschaften einer Datei zur Laufzeit zu ändern, sollten wir diese Datei irgendwo außerhalb des JARs platzieren. Dann werden wir Spring über die Befehlszeile mitteilen, wo es sich befindetParameter –spring.config.location = Datei: // {Pfad zur Datei} . Oder wir können es in application.properties einfügen.

In dateibasierten Eigenschaften müssen wir einen Weg wählen, um die Datei neu zu laden. Zum Beispiel können wir einen Endpunkt oder einen Scheduler entwickeln, um die Datei zu lesen und die Eigenschaften zu aktualisieren.

Eine praktische Bibliothek zum Neuladen der Datei ist die Commons-Konfiguration von Apache . Wir können PropertiesConfiguration mit verschiedenen ReloadingStrategy verwenden .

Fügen wir unserer pom.xml die Commons-Konfiguration hinzu :

 commons-configuration commons-configuration 1.10 

Anschließend fügen wir eine Methode zum Erstellen einer PropertiesConfiguration- Bean hinzu, die wir später verwenden werden:

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

Im obigen Code haben wir FileChangedReloadingStrategy als Neuladestrategie mit einer Standardaktualisierungsverzögerung festgelegt. Dies bedeutet, dass PropertiesConfiguration nach dem Änderungsdatum der Datei sucht, wenn die letzte Überprüfung vor 5000 ms erfolgte .

Wir können die Verzögerung mit FileChangedReloadingStrategy # setRefreshDelay anpassen.

3.1. Nachladen Umwelteigenschaften

Wenn wir die über eine Umgebungsinstanz geladenen Eigenschaften neu laden möchten , müssen wir die PropertySource erweitern und dann PropertiesConfiguration verwenden , um neue Werte aus der externen Eigenschaftendatei zurückzugeben .

Beginnen wir mit der Erweiterung der 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); } }

Wir haben die getProperty- Methode überschrieben , um sie an PropertiesConfiguration # getProperty zu delegieren . Daher wird in Intervallen entsprechend unserer Aktualisierungsverzögerung nach aktualisierten Werten gesucht.

Jetzt werden wir unsere ReloadablePropertySource zu den Eigenschaftsquellen von Environment hinzufügen :

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

Wir haben die neue Eigenschaftsquelle als erstes Element hinzugefügt, da sie alle vorhandenen Eigenschaften mit demselben Schlüssel überschreiben soll.

Erstellen wir eine Bean, um eine Eigenschaft aus der Umgebung zu lesen :

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

Wenn wir andere nachladbare externe Eigenschaftsquellen hinzufügen müssen, müssen wir zuerst unsere benutzerdefinierte PropertySourceFactory implementieren :

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

Dann können wir die Klasse einer Komponente mit @PropertySource kommentieren :

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

3.2. Eigenschafteninstanz neu laden

Umgebung ist eine bessere Wahl als Eigenschaften , insbesondere wenn Eigenschaften aus einer Datei neu geladen werden müssen. Wenn wir es jedoch brauchen, können wir die java.util.Properties erweitern :

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 }

Wir haben getProperty und seine Überladungen überschrieben und dann an eine PropertiesConfiguration- Instanz delegiert . Jetzt können wir eine Bean dieser Klasse erstellen und in unsere Komponenten einfügen.

3.3. Bean mit @ConfigurationProperties neu laden

Um den gleichen Effekt mit @ConfigurationProperties zu erzielen , müssten wir die Instanz rekonstruieren.

Spring erstellt jedoch nur eine neue Instanz von Komponenten mit Prototyp- oder Anforderungsbereich .

Daher funktioniert unsere Technik zum Neuladen der Umgebung auch für sie. Für Singletons haben wir jedoch keine andere Wahl, als einen Endpunkt zu implementieren, um die Bean zu zerstören und neu zu erstellen oder das Neuladen der Eigenschaften innerhalb der Bean selbst zu handhaben.

3.4. Bean mit @Value neu laden

Die Annotation @Value weist dieselben Einschränkungen auf wie @ConfigurationProperties .

4. Neuladen von Eigenschaften nach Aktor und 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.