Automatic Reloading of Configuration Sources
If an application has special requirements regarding its availability,
it is probably desired that changes on configuration files can be done
without the need for a restart. The application should automatically
detect such changes an react accordingly. This feature is referred to as
automatic reloading.
Providing support for automatic reloading is difficult because applications
may have very specific needs how and when to perform a reload. Also,
reloading should not only be limited to file-based configurations, but
work for other configuration sources as well; for instance for
configuration settings kept in a database. Therefore, Commons
Configuration provides some generic classes and interfaces that deal
with reloading. In the following section the basic concepts are discussed.
After that some more concrete scenarios are presented.
Components for Reloading
The reloading mechanism defined by Commons Configuration
involves multiple components which all work together to detect changes
on a configuration source and trigger the actual reload operation.
A basic component is a reloading detector, defined by the
ReloadingDetector
interface. This object is responsible for
detecting a change on an external configuration source. An example
implementation could check whether the last-modified data of a specific
file has changed. Note that a reloading detector does not have to monitor
a configuration source actively for changes; it only has to be able to
detect a change when it is triggered. This is reflected in the methods
defined by the ReloadingDetector
interface:
- The
isReloadingRequired()
method is called to trigger
a check. The detector has to determine whether something has changed on
the monitored source and returns a boolean flag as result.
- The
reloadingPerformed()
method is called after a
reload operation was performed. This method gives the detector the
opportunity to reset itself so that new changes on the associated
configuration source can be detected.
The next component taking part in reloading is an instance of the
ReloadingController
class. This is a fully functional class
implementing a generic protocol for executing a reload check (based on an
external trigger) and reacting accordingly. The actual check whether a
reload is required is delegated to a ReloadingDetector
associated with the controller. When the detector reports a change a
corresponding notification is sent out to registered reloading
listeners. Like ReloadingDetector
, a reloading
controller does not actively monitor a certain resource; it has a
checkForReloading()
method which has to be invoked in order
to trigger a reloading check. If this method returns true, the
controller changes into the so-called reloading state. This
means that the need for a reload was detected and now the reload has
actually to happen. Typically, this is done by one of the
ReloadingListener
objects registered at the controller. As long
as the controller is in reloading state, no further changes on the
configuration source monitored by the associated ReloadingDetector
are detected. A manual invocation of the resetReloadingState()
method is necessary to terminate this state and enable the detection of
further changes.
The components discussed so far only perform reload checks on demand. In
order to implement automatic reloading, it has to be ensured that the
checkForReloading()
method of a ReloadingController
is called periodically or at least when something happens which might
affect the monitored configuration source. This part of the reloading
mechanism is hard to provide in a generic form; in this area requirements
and use cases tend to be very specific. Therefore, Commons
Configuration just ships with a pretty simple, timer-based
solution; this may be sufficient in simple cases, for more complex
requirements it may be necessary to create a custom component triggering
a reload check.
After this theory, let's come to some examples how reloading of
configuration sources may be done in practice.
Reloading File-based Configurations
As was already stated, reloading is not limited to file-based
configurations. However, configuration files on the user's hard disk
which get changed by external sources are a typical use case for an
automatic reloading feature. Therefore, Commons Configuration
has some special support in this area. This is mainly provided by the
ReloadingFileBasedConfigurationBuilder
class, an extension of
the standard builder for file-based configurations.
ReloadingFileBasedConfigurationBuilder
already creates a
ReloadingController
and initializes it with a
ReloadingDetector
that is associated with the file managed
by the builder. (Actually, the situation is a bit more complex: the
creation of the reloading detector is delegated to an object implementing
the
ReloadingDetectorFactory
interface. The factory to be used can
be configured via the builder's initialization parameters. Per default, a
DefaultReloadingDetectorFactory
object is used which creates
an instance of the
FileHandlerReloadingDetector
class. Such an object can detect
changes on a file referenced by a FileHandler
.) The builder
is already registered as change listener at the reloading controller;
when the controller sends a notification that a change was detected the
builder resets itself. The next time the managed configuration is queried
from the builder, a fresh - updated - instance is returned. So the basic
components of a reloading setup are already in place. What is missing is
a periodic trigger initiating a reload check.
For this example we use the
PeriodicReloadingTrigger
class which is based on a scheduled
executor service. When constructing an instance of this class the
ReloadingController
and the period in which to trigger a
check have to be specified. Optionally, the
ScheduledExecutorService
can be provided; if this argument
is undefined, a default executor is created. It is also possible to pass
an arbitrary parameter object which will then be contained in change
events generated by the ReloadingController
. This is useful
if a component monitors multiple configuration sources which may be
reloaded. For this simple example this parameter is not used and therefore
set to null.
Note that the PeriodicReloadingTrigger
class - although fully
functional - may not be the right choice depending on the environment in
which an application is running. For instance, applications running in a
JEE container are typically not allowed to create threads; here a
different triggering mechanism has to be found.
Let's finally get to the code. We slightly adapt the example from the
section about
builders for file-based configurations. Goal is to load a properties
configuration from a file and enable a periodic reload check which
happens every minute:
Parameters params = new Parameters();
// Read data from this file
File propertiesFile = new File("config.properties");
ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.fileBased()
.setFile(propertiesFile));
PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(builder.getReloadingController(),
null, 1, TimeUnit.MINUTES);
trigger.start();
In this setup, the scheduler service used by the periodic trigger executes
a task every minute which asks the reloading builder's
ReloadingController
to perform a reload check. If the
underlying file has not been changed, this check has no effect. However,
if the file has been changed by an external source, an updated
last-modified date is detected, and the reloading detector signals a
need for a reload. This causes the reloading controller to fire a reloading
event to all registered listeners. The builder is itself registered as
reloading listener at its controller. In reaction to this event it resets
itself, so that the managed configuration becomes invalid. In addition,
a builder reset event is generated (see chapter
Configuration Events) which can notify
interested parties that an updated configuration is now available. The
next call to the builder's getConfiguration()
method causes
a new configuration instance to be created and initialized from the
content of the modified configuration file. At this time the reload
actually happens, and the controller's reloading state is reset.
The PeriodicReloadingTrigger
class has the methods
stop()
and start()
for pausing or resuming the
trigger. This may be useful if an application enters a state in which no
reload checks should be done - for instance during an update. When a
periodic trigger is no longer needed, its shutdown()
method
should be called which frees all resources and also terminates the
scheduled executor service gracefully.
One important point to keep in mind when using this approach to reloading
is that reloads are only functional if the builder is used as central
component for accessing configuration data. The configuration instance
obtained from the builder will not change automagically! So if an
application fetches a configuration object from the builder at startup
and then uses it throughout its life time, changes on the external
configuration file become never visible. The correct approach is to
keep a reference to the builder centrally and obtain the configuration
from there every time configuration data is needed.
Users familiar with older versions of Commons Configuration will
notice that this is a fundamental change compared to the old reloading
implementation. In the old implementation a reload could happen at any
time on a configuration the application was operating on. This had the
advantage that it was fully transparent to the application. But on the
other hand, the application had no control over the reloading mechanism.
With the new approach, an application can obtain a configuration object
from a builder and then perform a unit of work with this instance. As long
as the builder is not accessed any more during this unit of work, it is
guaranteed that the data in the configuration is not changing in an
uncontrolled way due to a reload operation. This gives the access to
configuration data a kind of "transactional" behavior.
Builder Configuration Related to Reloading
When setting up a configuration builder with reloading support for
file-based configurations some settings can be defined that influence
reloading operations. These settings are part of the initialization
parameters for file-based configurations and defined by the
FileBasedBuilderProperties
interface:
- The
ReloadingDetectorFactory
to be used when the reloading
controller is created. An application with special requirements related
to the detection of changes can here provide a custom factory. As was
mentioned above, the default factory creates a suitable detector for
detecting changes on a file.
- The so-called reloading refresh delay. This is a numeric
value in milliseconds limiting the access to the underlying file. The
reloading detector will check for updates on the file only if the last
check was not within the time span defined by the refresh delay. This
value can be used to improve performance if there are many accesses to
a configuration builder in short intervals.
Generic Reloading Support
In fact,
ReloadingFileBasedConfigurationBuilder
is a pretty thin
implementation around a generic reloading mechanism already supported by
the
BasicConfigurationBuilder
base class. What it does is mainly
specific to file-based configurations: It ensures that a suitable
ReloadingDetector
is used which is connected to the file
managed by the builder, and that this detector is used by a
ReloadingController
object also created by the builder.
It is pretty easy to make use of the same generic reloading support to
enable reloading functionality for other types of configuration builders
as well. The key to this lies in the method
connectToReloadingController()
provided by
BasicConfigurationBuilder
. This method expects a
ReloadingController
object as argument. It performs some
event listener registrations to ensure that reloading events fired by
the controller cause the builder's result object to be invalidated, and
that the creation of a new result object causes the controller's
reloading state to be reset. In a nutshell, this is a full implementation
of the reloading protocol.
So the recipe to activate reloading for a builder instance is as follows:
- Create and initialize the builder instance as usual.
- Create a
ReloadingDetector
which is able to monitor
the configuration source in question and to find out whether a reload
action has to be performed. For this probably a custom implementation is
required (as Commons Configuration currently supports only
reloading detector implementations dealing with file handlers).
- Create a
ReloadingController
object and initialize it
with the ReloadingDetector
created in the previous step.
- Pass this reloading controllers to the builder's
connectToReloadingController()
method.
- Now reloading facilities are set up for this builder. In order to
actually trigger reload checks ensure that the reloading controller's
checkForReloading()
method is called at appropriate points
of time (e.g. initiate a corresponding trigger as described earlier in
this chapter.
Reloading Checks on Builder Access
For some applications it may not be necessary to react on external
changes on their configuration immediately. It just has to be ensured
that when an access to configuration data is performed, the most recent
settings are read. This is in principle similar to the mechanism
implemented in Commons Configuration 1.x; here checks for reloads
were triggered by each access to a configuration property - and only by
that.
It is possible to set up a configuration builder in a way that each time
the getConfiguration()
method is called a reloading check
is performed. If the reloading controller detects that the monitored
source has changed, the managed configuration is replaced by an updated
one. So the builder returns the fresh configuration instance. If used
this way, no special reloading trigger has to be installed; reloading
can only happen when the builder is queried for its managed configuration.
But then it is guaranteed that an up-to-date configuration instance is
returned. Note the main difference to the old model as used in
Commons Configuration 1.x: Only invocations of a builder's
getConfiguration()
method trigger a reloading check, not
access to the managed configuration's properties.
In order to configure a configuration builder to trigger reloading checks
each time its managed configuration is accessed, a special event generated
by the builder can be used: the configuration request event.
This event is passed to registered event listeners before the managed
configuration is accessed. (More information about event listeners can
be found in the chapter about events.)
A listener for this event just has to trigger a reloading controller.
This will cause the managed configuration to be invalidated and replaced
before it is returned to the caller. The following example shows how this
can be achieved. It makes use of a ReloadingFileBasedConfigurationBuilder
because this class provides easy access to its associated reloading
controller. However, the same principle also works for other builders
connected to a reloading controller (as described in the previous section):
// Create and initialize the builder
final ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(...);
// Register an event listener for triggering reloading checks
builder.addEventListener(ConfigurationBuilderEvent.CONFIGURATION_REQUEST,
new EventListener()
{
@Override
public void onEvent(Event event)
{
builder.getReloadingController().checkForReloading(null);
}
});
Managed Reloading
ManagedReloadingDetector
is an alternative to automatic
reloading. It allows to hot-reload properties on a running application,
but only when requested by an administrator. The detector class defines a
refresh()
method which forces a reload of the configuration
source the next time a reloading check on the associated
ReloadingController
is triggered.
A typical use of this feature is to setup ManagedReloadingDetector
as a JMX MBean. The following code sample uses Spring framework's
MBeanExporter
to expose the managed reloading detector to the
JMX console:
<!-- The managed reloading detector for the configuration builder -->
<bean id="reloadingDetector" class="...ManagedReloadingDetector"/>
<!-- The MBeanExporter that exposes reloadingDetector to the JMX console -->
<bean id="mbeanMetadataExporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server" ref="mbeanServer"/>
<property name="beans">
<map>
<entry key="myApp:bean=configuration" value-ref="reloadingStrategy"/>
</map>
</property>
</bean>
With this configuration, the JMX console will expose the
"myApp:bean=configuration" MBean and it's refresh operation.