spring

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

  1. Spring 4.1.4
  2. Apache Commons Configuration 1.10
  3. Spring Test 4.1.4
  4. JUnit 4.11
  5. Apache Maven 3.0.5
  6. JDK 1.8
  7. Eclipse 4.4 (Luna)

2. Project Structure

We create a simple Spring Maven project with the following Structure.

project-structure
Figure 1: Project 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:

  1. Creating a new PropertiesConfiguration object with the given application.properties file path.
    String filePath = PropertiesConstants.PROPERTIES_FILE_PATH;
    System.out.println("Loading the properties file: " + filePath);
    configuration = new PropertiesConfiguration(filePath);
    
  2. Create new FileChangedReloadingStrategy for the previously created PropertiesConfiguration to reload the application.properties file based on a predefined time interval REFRESH_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:

  1. getProperty(String key): Gets a property value from the application.properties file.
  2. setProperty(String key, Object value): Set a new property value, this will replace any previously set values.
  3. 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.

Download
You can download the full source code of this example here: AutoReloadableSpringProperties.zip

Ashraf Sarhan

Ashraf Sarhan is a passionate software engineer, an open source enthusiast, has a Bsc. degree in Computer and Information Systems from Alexandria University. He is experienced in building large, scalable and distributed enterprise applications/service in multiple domains. He also has a keen interest in JavaEE, SOA, Agile and Big Data technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Amol
Amol
2 years ago

What if values are comma separated ?

Back to top button