Auto reload of configuration when any change happen

In this post, i am writing about a solution of very common problem. Every application has some configuration which is expected to be refreshed on every change in configuration file. Past approaches to solve this problem had consisted of having a Thread, which periodically poll for file change based on ‘last update time stamp’ of configuration file.

Now with java 7, things have changed. Java 7 has introduced an excellent feature: WatchService. I will try to give you a potential solution for above problem. This may not be the best implementation, but it will surely give a very good start for your solution. I bet !!

Sections in this post

1) A brief overview of WatchService
2) Writing our configuration provider
3) Introducing configuration change listener
4) Testing our code
5) Key notes

A brief overview of WatchService

A WatchService is JDKs internal service which watches for changes on registered objects. These registered objects are necessarily the instances of Watchable interface. When registering the watchable instance with WatchService, we need to specify the kind of change events we are interested in.

There are four type of events a of now: ENTRY_CREATEENTRY_DELETEENTRY_MODIFY and OVERFLOW. You can read about these events in provided links.

WatchService interface extends Closeable interface, means service can be closed as and when required. Normally, it should be done using JVM provided shut down hooks.

Writing our Configuration provider

A configuration provider is simply a wrapper for holding the set of properties in java,util.Properties instance. It also provides methods to get the configured properties using their KEY.

A sample implementation goes as below :

package testWatchService;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class ApplicationConfiguration {
	private final static ApplicationConfiguration INSTANCE = new ApplicationConfiguration();

	public static ApplicationConfiguration getInstance() {
		return INSTANCE;
	}

	private static Properties configuration = new Properties();

	private static Properties getConfiguration() {
		return configuration;
	}

	public void initilize(final String file) {
		InputStream in = null;
		try {
			in = new FileInputStream(new File(file));
			configuration.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public String getConfiguration(final String key) {
		return (String) getConfiguration().get(key);
	}

	public String getConfigurationWithDefaultValue(final String key,
			final String defaultValue) {
		return (String) getConfiguration().getProperty(key, defaultValue);
	}
}

Introducing configuration change listener

Now when we have our basic wrapper for our in memory cache of configuration properties, we need a mechanism to reload this cache on run time, whenever configuration file stored in file system changes.

I have written a sample working code for your help:

package testWatchService;

import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class ConfigurationChangeListner implements Runnable {
	private String configFileName = null;
	private String fullFilePath = null;

	public ConfigurationChangeListner(final String filePath) {
		this.fullFilePath = filePath;
	}

	public void run() {
		try {
			register(this.fullFilePath);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void register(final String file) throws IOException {
		final int lastIndex = file.lastIndexOf("/");
		String dirPath = file.substring(0, lastIndex + 1);
		String fileName = file.substring(lastIndex + 1, file.length());
		this.configFileName = fileName;

		configurationChanged(file);
		startWatcher(dirPath, fileName);
	}

	private void startWatcher(String dirPath, String file) throws IOException {
		final WatchService watchService = FileSystems.getDefault()
				.newWatchService();
		Path path = Paths.get(dirPath);
		path.register(watchService, ENTRY_MODIFY);

		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				try {
					watchService.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});

		WatchKey key = null;
		while (true) {
			try {
				key = watchService.take();
				for (WatchEvent< ?> event : key.pollEvents()) {
					if (event.context().toString().equals(configFileName)) {
						configurationChanged(dirPath + file);
					}
				}
				boolean reset = key.reset();
				if (!reset) {
					System.out.println("Could not reset the watch key.");
					break;
				}
			} catch (Exception e) {
				System.out.println("InterruptedException: " + e.getMessage());
			}
		}
	}

	public void configurationChanged(final String file) {
		System.out.println("Refreshing the configuration.");
		ApplicationConfiguration.getInstance().initilize(file);
	}
}

Above class is created using a thread which will be listening to configuration properties file changes using WatchService.
Once, it detects any modification in file, it simply refresh the in memory cache of configuration.

The constructor of above listener takes only one parameter i.e. fully qualified path of monitored configuration file. Listener class is notified immediately when configuration file is changed in file system.

This listener class  then call ApplicationConfiguration.getInstance().initilize(file); to reload in memory cache.

Testing our code

Now, when we are ready with our classes, we will test them.

First of all, store a test.properties file with following content in c:/Lokesh/temp folder.

TEST_KEY=TEST_VALUE

Now, test above classes using below code.

package testWatchService;

public class ConfigChangeTest {
	private static final String FILE_PATH = "C:/Lokesh/temp/test.properties";

	public static void main(String[] args) {
		ConfigurationChangeListner listner = new ConfigurationChangeListner(
				FILE_PATH);
		try {
			new Thread(listner).start();
			while (true) {
				Thread.sleep(2000l);
				System.out.println(ApplicationConfiguration.getInstance()
						.getConfiguration("TEST_KEY"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Output of above program (Change the TEST_VALUE to TEST_VALUE1 and TEST_VALUE2 using any file editor and save) ::

Refreshing the configuration.

TEST_VALUE

TEST_VALUE

TEST_VALUE

Refreshing the configuration.

TEST_VALUE1

Refreshing the configuration.

TEST_VALUE2

Above outputs show that every time we make any change to property file, properties loaded are refreshed and new property value is available to use. Good work done so far !!

Key notes

1) If you are using java 7 in your new project, and you are not using old fashioned methods to reload your properties, you are not doing it rightly.

2) WatchService provides two methods take() and poll(). While take() method wait for next change to happen and until it is blocked, poll() immediately check for change event.

If nothing changed from last poll() call, it will return null. poll() method does not block the execution, so should be called in a Thread with some sleep time.

Happy Learning !!

你可能感兴趣的:(configuration)