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
PropertiesConfigurationobject with the givenapplication.propertiesfile path.String filePath = PropertiesConstants.PROPERTIES_FILE_PATH; System.out.println("Loading the properties file: " + filePath); configuration = new PropertiesConfiguration(filePath); - Create new
FileChangedReloadingStrategyfor the previously createdPropertiesConfigurationto reload theapplication.propertiesfile based on a predefined time intervalREFRESH_DELAY = 1000to 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.propertiesfile.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 ?