- Mutability Support
An in-memory config tree, once loaded, is immutable, even though the data in the underlying config sources can change over time. Your application can find out metadata about a loaded in-memory config and can track changes in config sources.
Overview
Even though in-memory config trees are immutable, the config system internally records which config sources it used to load each config tree and some metadata about the configuration. Your application can be aware of updates to the underlying config sources by:
- using the metadata the config system maintains,
- responding to changes when the config sources are updated, or
- using
Suppliers of particular config values to obtain the always-current value for a key.
Using Config Metadata
Loading Time
The config system records when it loads each configuration into memory. Your application can retrieve it by invoking the timestamp method:
java.time.Instance loadTime = myConfig.timestamp();on any config node.
Config Context
The config system maintains a Config.Context for each Config node. Your application can retrieve the context by invoking the Config.context() method and then use it for these operations:
Config.Context| Method | Usage |
|---|---|
Instant timestamp() | Returns the load time of the last loaded configuration that used the context. |
Config last() | Returns the most recently loaded configuration that used the context. |
Config reload() | Reloads the entire config tree from the current contents of the same config sources used to load the tree in which the current node resides. |
Note that the config context describes or replaces a currently-loaded config tree. It by itself does not help your application decide when reloading the config might be useful.
Responding to Changes in Config Sources
Evolving API
This section describes the Config.changes() method. It is marked as deprecated because it returns an io.helidon.reactive.Flow.Publisher object. In a future Helidon release that requires Java 11 or later this method will be undeprecated and changed — or a similar method will be added — so that the return type is java.util.concurrent.Flow.Publisher instead.
Any code you write using the existing Config.changes() method might need to change at that time.
Although in-memory config trees do not change once loaded, applications can respond to changes in the underlying config sources by:
- setting up change detection for the config sources used to build a configuration, and
- registering a response to be run when a source changes.
Your code’s response can react to the changes in whatever way makes sense for your application.
The following sections describe these steps in detail.
Setting up Config Source Change Detection
When the application creates a config source, it can set up change detection for that source. This is called polling in the Helidon API but specific change detection algorithms might not use actual polling. You choose a specific PollingStrategy for each config source you want to monitor. See the section on polling strategies in the config extensions doc page for more information.
The config system provides some built-in polling strategies, exposed as these methods on the PollingStrategies class:
regular(Duration interval)- a general-purpose scheduled polling strategy with a specified, constant polling interval.watch(Path watchedPath)- a filesystem-specific strategy to watch specified path. You can use this strategy with thefileandclasspathbuilt-in config sources.nop()- a no-op strategy
This example builds a Config object from three sources, each set up with a different polling strategy:
Config with a different PollingStrategy for each config sourceConfig config = Config.create(
ConfigSources.file("conf/dev.properties")
.pollingStrategy(PollingStrategies.regular(Duration.ofSeconds(2)))
.optional(),
ConfigSources.file("conf/config.properties")
.changeWatcher(FileSystemWatcher.create())
.optional(),
ConfigSources.classpath("application.properties")
.pollingStrategy(PollingStrategies::nop)); - Optional
filesourceconf/dev.propertieswill be checked for changes every2seconds. - Optional
filesourceconf/config.propertieswill be watched by the JavaWatchServicefor changes on filesystem. - The
classpathresourceapplication.propertieswill not be checked for changes.PollingStrategies.nop()polling strategy is default.
The polling strategies internally inform the config system when they detect changes in the monitored config sources (except that the nop strategy does nothing).
Registering a Config Change Response
To know when config sources have changed, your application must register its interest on the Config node of interest. The config system will then notify your application of any change within the subtree rooted at that node. In particular, if you register on the root node, then the config system notifies your code of changes anywhere in the config tree.
You can register in either of two ways:
- register an action to be run upon each change, or
- subscribe to a
Flow.Publisherthat notifies of changes.
Registering Actions
A simple approach is for your application to register a function that should run when any change occurs.
greeting property changes via onChange methodconfig.get("greeting")
.onChange(changedNode -> {
System.out.println("Node " + changedNode.key() + " has changed!");
});- Navigate to the
Confignode on which you want to register. - Invoke the
onChangemethod, passing a consumer (Consumer<Config>). The config system invokes that consumer each time the subtree rooted at thegreetingnode changes. ThechangedNodeis a new instance ofConfigrepresenting the updated subtree rooted atgreeting.
Subscribing to Events
The config system also supports the flow publisher/subscriber model for applications that need more control over the pace at which the config system delivers config change events.
Each Config instance exposes the Config.changes() method which returns a Flow.Publisher<Config>. Your application can invoke this method, then invoke subscribe on the returned Flow.Publisher, passing your own Flow.Subscriber implementation. The config system will invoke your subscriber’s methods as appropriate, most notably calling onNext whenever it detects a change in one of the underlying config sources for the config node of interest.
Mote that your subscriber will be notified when a change occurs anywhere in the subtree represented by the Config node.
greeting property changesconfig.get("greeting")
.changes()
.subscribe(new Flow.Subscriber<>() {
Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(Config changedNode) {
System.out.println("Node " + changedNode.key() + " has changed!");
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
});- Navigate to the
Confignode on which you want to register. - Invoke
changesto get theFlow.Publisherof changes to the subtree rooted at theConfignode. - Subscribe to the publisher passing a custom
Flow.Subscriber<Config>implementation. - Request the first event delivery in
onSubscribemethod. - The config system invokes
onNexteach time the subtree rooted at thegreetingnode changes. ThechangedNodeis a new instance ofConfigrepresenting the updated subtree rooted atgreeting, regardless of where in the subtree the change actually occurred. Remember to request the next event delivery inonNext. - The config system does not currently invoke
onError. - The config system invokes
onCompleteif all config sources indicate there will be no other change event.
Note
Your application does not need to subscribe to the new Config instance passed to your onNext method. The original subscription remains in force for changes to the "new" instance.
Accessing Always-current Values
Some applications do not need to respond to changes as they happen. Instead it’s sufficient that they simply have access to the current value for a particular key in the configuration.
Each asXXX method on the Config class has a companion asXXXSupplier method. These supplier methods return Supplier<XXX>, and when your application invokes the supplier’s get method the config system returns the then-current value as stored in the config source.
greeting property as Supplier// Construct a Config with the appropriate PollingStrategy on each config source.
Supplier<String> greetingSupplier = config.get("greeting")
.asString().supplier();
System.out.println("Always actual greeting value: " + greetingSupplier.get()); - Navigate to the
Confignode for which you want access to the always-current value. - Retrieve and store the returned supplier for later use.
- Invoke the supplier’s
get()method to retrieve the current value of the node.
Important
Supplier support requires that you create the Config object from config sources that have proper polling strategies set up.