Reloadable properties file with Spring using Apache Commons Configuration
In this example we shall show you how to create an auto-reloadable application properties file with Spring using Apache Commons Configuration. Our previous example, shows how to load specific environment configurations and properties using Spring.
However, when we make some changes in the properties files, we have to rebuild and redeploy our application again. Although, this approach doesn’t fit all types of application properties, some properties cannot meaningfully be changed dynamically at runtime like properties configuring resources, database URLs, JMS queue names, these properties are used to source out configuration data from the application.
On the other hand, there is another type of properties which can fit this approach, a client side properties which may determine application behavior like different application running modes and they can be set later by somebody / something standing outside the application, those properties are volatile so it doesn’t make sense to rebuild/redeploy your application whenever the client makes some changes in those properties.
So, our example will show how to make an auto-reloadable application properties so whenever you change the application properties file, your application will feel these changes and reload the properties again.
1. Project Environment
- Spring 4.1.4
- Apache Commons Configuration 1.10
- Spring Test 4.1.4
- JUnit 4.11
- Apache Maven 3.0.5
- JDK 1.8
- Eclipse 4.4 (Luna)
2. Project Structure
We create a simple Spring Maven project with the following Structure.
3. Project Dependencies
We have the following Dependencies in our below POM
file.
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.samples</groupId> <artifactId>autoreloadablespringproperties-example-code</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>Auto Reloadable Spring Properties Example Code</name> <properties> <!-- Generic properties --> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <resource.directory>src/main/resources</resource.directory> <!-- Spring --> <spring-framework.version>4.1.4.RELEASE</spring-framework.version> <!-- Logging --> <log4j.version>1.2.17</log4j.version> <!-- Test --> <junit.version>4.11</junit.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- Logging with Log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <!-- Test Artifacts --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- Apache Commons Configuration --> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.10</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> </project>
4. Properties File
We created the following properties file in /home
directory, it contains a client related properties which are always located outside the project. Also, it may be updated by the client while the project is running.
application.properties:
# Client Properties mode=active host=localhost port=8080 user=admin password=admin
5. ApplicationProperties Spring Component
We create ApplicationProperties.java
as a Spring component class annotated by @Component
which will be a singleton bean contains all your reloadable properties.
As you notice that we have an init()
annotated by @PostConstruct
, this method will be executed after dependency injection is done to perform the following initialization:
- Creating a new
PropertiesConfiguration
object with the givenapplication.properties
file path.String filePath = PropertiesConstants.PROPERTIES_FILE_PATH; System.out.println("Loading the properties file: " + filePath); configuration = new PropertiesConfiguration(filePath);
- Create new
FileChangedReloadingStrategy
for the previously createdPropertiesConfiguration
to reload theapplication.properties
file based on a predefined time intervalREFRESH_DELAY = 1000
to get any updates.FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy(); fileChangedReloadingStrategy.setRefreshDelay(PropertiesConstants.REFRESH_DELAY); configuration.setReloadingStrategy(fileChangedReloadingStrategy);
The ApplicationProperties
class has the following three methods:
getProperty(String key)
: Gets a property value from theapplication.properties
file.setProperty(String key, Object value)
: Set a new property value, this will replace any previously set values.save()
: Save the configuration. Before this method can be called a valid file name must have been set.
ApplicationProperties.java:
package com.jcg.prop; import javax.annotation.PostConstruct; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; import org.springframework.stereotype.Component; /** * @author ashraf_sarhan * */ @Component public class ApplicationProperties { private PropertiesConfiguration configuration; @PostConstruct private void init() { try { String filePath = PropertiesConstants.PROPERTIES_FILE_PATH; System.out.println("Loading the properties file: " + filePath); configuration = new PropertiesConfiguration(filePath); //Create new FileChangedReloadingStrategy to reload the properties file based on the given time interval FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy(); fileChangedReloadingStrategy.setRefreshDelay(PropertiesConstants.REFRESH_DELAY); configuration.setReloadingStrategy(fileChangedReloadingStrategy); } catch (ConfigurationException e) { e.printStackTrace(); } } public String getProperty(String key) { return (String) configuration.getProperty(key); } public void setProperty(String key, Object value) { configuration.setProperty(key, value); } public void save() { try { configuration.save(); } catch (ConfigurationException e) { e.printStackTrace(); } } }
Also, we have a supplementary class PropertiesConstants.java
which contains some constants which are used through the code like properties file path, refresh delay and properties keys.
PropertiesConstants.java:
package com.jcg.prop; /** * @author ashraf_sarhan * */ public class PropertiesConstants { public static final String PROPERTIES_FILE_PATH = System .getProperty("user.home") + "/application.properties"; public static final int REFRESH_DELAY = 1000; public static final String MODE = "mode"; public static final String HOST = "host"; public static final String PORT = "port"; public static final String USER = "user"; public static final String PASSWORD = "password"; public static final String ACTIVE_MODE = "active"; public static final String IDLE_MODE = "idle"; }
Finally, to allow the autowiring of our ApplicationProperties
Spring component, we created the following Spring context file.
app-context.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- scans for annotated classes in the com.company package --> <context:component-scan base-package="com.jcg" /> <!-- enables annotation based configuration --> <context:annotation-config /> </beans>
5. ApplicationProperties Unit Test
Now, it’s the time for testing our code, we created AutoReloadablePropertiesTest.java
as a unit test class to test our code. Simply, it runs tow Thread (PropReaderThread
, PropEditorThread
) where the PropReaderThread.java
plays as a property reader where it continuously reads the (host
, port
, user
, password
) every 1000
ms if the mode
value is active
.
On the other side, the PropEditorThread.java
plays as a mode
property editor where it continuously updates the mode
value from active
to idle
every 3000
ms and visa versa, then it calls the save()
method to save the ApplicationProperties
. So, our ApplicationProperties
class feels the changes and reload its properties.
As you notice, we just automate the properties file updating processes for the unit test through using PropEditorThread
but in the real case, this process will be done by somebody on the client side.
AutoReloadablePropertiesTest.java:
package com.jcg.test; import junit.framework.TestCase; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.jcg.prop.ApplicationProperties; /** * @author ashraf * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/app-context.xml") public class AutoReloadablePropertiesTest extends TestCase { private static final int MAIN_THREAD_SLEEP_TIME = 10000; private static final int PROP_READER_THREAD_SLEEP_TIME = 1000; private static final int PROP_EDITOR_THREAD_SLEEP_TIME = 3000; @Autowired private ApplicationProperties applicationProperties; @Test public void testAppProperties() { try { // Start three PropReaderThread to read specific property using ApplicationProperties new PropReaderThread("PropReader", PROP_READER_THREAD_SLEEP_TIME, applicationProperties); // Start three PropEditorThread to update the mode property using ApplicationProperties new PropEditorThread("PropEditor", PROP_EDITOR_THREAD_SLEEP_TIME, applicationProperties); // This main will sleep for one minute then it will exit. Thread.sleep(MAIN_THREAD_SLEEP_TIME); } catch (InterruptedException e) { System.out.println("Main thread Interrupted"); } System.out.println("Main thread was finished!"); } }
PropReaderThread.java:
package com.jcg.test; import com.jcg.prop.ApplicationProperties; import com.jcg.prop.PropertiesConstants; /** * @author ashraf * */ public class PropReaderThread implements Runnable { // Thread name private String name; private int sleepTime; private ApplicationProperties applicationProperties; private Thread t; private int counter = 1; public PropReaderThread(String threadname, int sleepTime, ApplicationProperties applicationProperties) { this.name = threadname; this.sleepTime = sleepTime; this.applicationProperties = applicationProperties; t = new Thread(this, name); System.out.println(t); // Start the thread t.start(); } public void run() { while (true) { try { if (PropertiesConstants.ACTIVE_MODE .equals(applicationProperties .getProperty(PropertiesConstants.MODE))) { System.out.println(name + " Thread (request: " + counter + "): " + " [ host: " + applicationProperties .getProperty(PropertiesConstants.HOST) + ", port: " + applicationProperties .getProperty(PropertiesConstants.PORT) + ", user: " + applicationProperties .getProperty(PropertiesConstants.USER) + ", password: " + applicationProperties .getProperty(PropertiesConstants.PASSWORD) + " ]"); } else { System.out.println(name + " Thread (request: " + counter + "): Client disabled the active mode!"); } counter++; Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } } } }
PropEditorThread.java:
package com.jcg.test; import com.jcg.prop.ApplicationProperties; import com.jcg.prop.PropertiesConstants; /** * @author ashraf * */ public class PropEditorThread implements Runnable { // Thread name private String name; private int sleepTime; private ApplicationProperties applicationProperties; private Thread t; public PropEditorThread(String threadname, int sleepTime, ApplicationProperties applicationProperties) { this.name = threadname; this.sleepTime = sleepTime; this.applicationProperties = applicationProperties; t = new Thread(this, name); System.out.println(t); // Start the thread t.start(); } public void run() { while (true) { try { String mode = applicationProperties .getProperty(PropertiesConstants.MODE); if (PropertiesConstants.ACTIVE_MODE .equals(mode)) { applicationProperties.setProperty(PropertiesConstants.MODE, PropertiesConstants.IDLE_MODE); System.out.println(name + " thread updates the mode property to " + PropertiesConstants.IDLE_MODE); } else if (PropertiesConstants.IDLE_MODE .equals(mode)) { applicationProperties.setProperty(PropertiesConstants.MODE, PropertiesConstants.ACTIVE_MODE); System.out.println(name + " thread updates the mode property to " + PropertiesConstants.ACTIVE_MODE); } applicationProperties.save(); Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Output:
Loading the properties file: /home/ashraf/application.properties Thread[PropReader,5,main] PropReader Thread (request: 1): Client disabled the active mode! Thread[PropEditor,5,main] PropEditor thread updates the mode property to active PropReader Thread (request: 2): [ host: localhost, port: 8080, user: admin, password: admin ] PropReader Thread (request: 3): [ host: localhost, port: 8080, user: admin, password: admin ] PropReader Thread (request: 4): [ host: localhost, port: 8080, user: admin, password: admin ] PropEditor thread updates the mode property to idle PropReader Thread (request: 5): Client disabled the active mode! PropReader Thread (request: 6): Client disabled the active mode! PropReader Thread (request: 7): Client disabled the active mode! PropEditor thread updates the mode property to active PropReader Thread (request: 8): [ host: localhost, port: 8080, user: admin, password: admin ] PropReader Thread (request: 9): [ host: localhost, port: 8080, user: admin, password: admin ] PropReader Thread (request: 10): [ host: localhost, port: 8080, user: admin, password: admin ] PropEditor thread updates the mode property to idle Main thread was finished! PropReader Thread (request: 11): Client disabled the active mode!
Download the Source Code of this example
This was an example on how to create an auto-reloadable application properties with Spring using Apache Commons Configuration.
You can download the full source code of this example here: AutoReloadableSpringProperties.zip
What if values are comma separated ?