Java Tip 125: Set your timer for dynamic properties

Reconfigure your application at runtime with reloadable property files

Property files are widely used to control application configurations. One great advantage of property files: they let you change your application's configuration without recompilation. However, you most likely need to restart your application for the new configuration to take effect. This might not be a constraint for client applications, where users stop and start the application. In fact, some GUI (graphical user inferface) programs even let you change properties through the user interface, without restarting the program.

However, for server applications, where often a GUI-deficient property file set controls the configuration, changing configuration parameters without restarting the application is more desirable -- though more challenging.

For example, suppose your application emits different log-message levels depending on the logLevel parameter's value. During normal operation, you'd set logLevel to the lowest value for best performance. But if something unusual happens, you might want to change logLevel to a higher value for debugging purposes. In the process, you don't want to leave your hundreds, maybe thousands, of users stranded.

As another example, you might want such capability during your servlet development, where you lack control when your servlet loads or unloads. If new properties load automatically, you won't need to stop and start the application server to change one servlet's configuration.

A typical, full-blown solution to this problem exposes the application properties through RMI (Remote Method Invocation) or HTTP. That way, you can manage the application configuration at runtime with either a Swing-based GUI or the Web interface. Of course, implementing such a solution requires much effort, and such areas as application security requires your utmost attention.

In this tip, I'll show you a much simpler solution that uses auto-reloadable property files. Thomas E. Davis proposed a similar solution in "How to Easily Reconfigure Your Applications -- While They're Running," where he used a database to store the reloadable properties.

Properties file basics

You can manage application properties with Java's Properties class in its java.util package. You can use the Properties class to load application properties from any InputStream, such as a file, a jar archive, or a network connection. A plain-text properties file is commonly used. The following code shows how you use a properties file:

1       import java.util.*;
2       import java.io.*;
3
4       public class Example {
5           private String emailAddr;
6           private int logLevel;
7
8           public Example() throws IOException {
9               loadProperties();
10          }
11
12          public String getEmailAddress() {
13              return emailAddr;
14          }
15
16          public synchronized void setEmailAddress(String email) {
17              emailAddr = email;
18          }
19
20          public int getLogLevel() {
21              return logLevel;
22          }
23
24          public synchronized void setLogLevel(int level) {
25              logLevel = level;
26          }
27
28          private void loadProperties() throws IOException {
29              Properties props = new Properties();
30              InputStream in = new
FileInputStream("example.properties");
31              props.load(in);
32              in.close();
33
34              setEmailAddress(props.getProperty("emailAddress"));
35              setLogLevel(Integer.parseInt(props.getProperty
("logLevel")));
36          }
37      }

The example.properties file looks like this:

1       emailAddress=admin@corporate.com
2       logLevel=4

As shown, the properties file loads when the object instantiates. You can easily change either logLevel or emailAddress by modifying them in the properties file. However, changing the properties file after object creation does not affect that object's attributes. You must restart your application to make the new properties effective.

Note that in the example above the setters are made public, which lets you change the properties programmatically. This might or might not be desirable depending on your specific situation. If you plan to expose the properties via RMI or HTTP and use a Swing-based GUI or Web interface to manage the application, you should leave them public. On the other hand, using this tip's technique, you should disallow programmatic property changes by making properties setters private or protected, because the programmatically changed properties will be overridden when you auto-reload them from the properties file.

Normally, you can put property files on a relative or absolute path. But if you desire more location independence and flexibility, you can also put them on the CLASSPATH and get an InputStream like this:

    this.getClass().getClassLoader().getResourceAsStream
("my.properties");

The Example class provided in the source code's util/test directory demonstrates this technique; see jw-javatip125.zip.

Dynamic properties

To use the new values from a modified properties file without restarting, you just reload the properties whenever the file changes. The solution is simple: you monitor the properties file, and if the file changes, you reload the properties. Therefore, you need a file monitor to detect file changes and a callback interface to notify any changes to a monitored properties file.

However, two problems remain:

  1. How do you minimize code changes to utilize dynamic properties?
  2. How do you minimize the monitoring activity's impact on application performance?

You satisfy the first problem by delegating the monitoring to a dedicated file monitor. You satisfy the second by using two classes from the JDK 1.3 java.util package: Timer and TimerTask.

FileChangeListener.java in the source code defines the following callback interface:

1       public interface FileChangeListener {
2           public void fileChanged(String fileName);
3       }

The file monitor must implement two public methods:

  1. addFileChangeListener, which adds a file with its callback listener to the file monitor for monitoring

  2. removeFileChangeListener, which removes a monitored file/callback pair from the file monitor

Here's the relevant code for FileMonitor:

1           public void addFileChangeListener(FileChangeListener
listener,
2                                             String fileName,
3                                             long period)
4                               throws FileNotFoundException {
5               removeFileChangeListener(listener, fileName);
6               FileMonitorTask task = new FileMonitorTask(listener,
fileName);
7               timerEntries.put(fileName + listener.hashCode(), task);
8               timer.schedule(task, period, period);
9           }
10
11          public void removeFileChangeListener(FileChangeListener
listener,
12                                               String fileName) {
13              FileMonitorTask task = (FileMonitorTask)
14                                      timerEntries.remove(fileName
15                                                      +
listener.hashCode());
16              if (task != null) {
17                  task.cancel();
18              }
19          }

The jw-javatip125.zip file's FileMonitor.java contains this implementation's full source code.

In line 5 above, removeFileChangeListener is called from within addFileChangeListener, so that addFileChangeListener can be called repeatedly without adding multiple monitoring tasks for the same listener/file combination.

In line 8 above, timer is an instance of java.util.Timer. The monitoring task is scheduled to run repetitively with a fixed delay of period milliseconds between consecutive executions, beginning after period milliseconds.

As you can see, each time addFileChangeListener is called, a new FileMonitorTask is created and scheduled with timer. Each time removeFileChangeListener is called, a scheduled task is canceled.

FileMonitorTask is an inner class that extends the abstract class TimerTask. The FileMonitorTask class's run() method, which does the actual work scheduled, checks to see if the timestamp for the monitored file changed, and if so, notifies the listener:

1       public void run() {
2           long lastModified = monitoredFile.lastModified();
3           if (lastModified != this.lastModified) {
4               this.lastModified = lastModified;
5               fireFileChangeEvent(this.listener, this.fileName);
6           }
7       }

Since the scheduled task only checks a file's timestamp, it can run fast. The overhead of file monitoring activity is low, because the Timer class uses one background thread to execute all scheduled tasks. Internally, it tracks the execution order with a binary heap and can scale to large numbers of concurrently scheduled tasks.

Judge the impact of dynamic reconfiguration

To utilize the new dynamic properties, you must make minimal code changes to the first example:

  1. Change line 4 to:

        public class Example implements FileChangeListener
    
  2. Add the following after line 9:

        FileMonitor.getInstance().addFileChangeListener(this,
    "example.properties", 5000);
    
  3. Implement the FileChangeListener interface:
        public void fileChanged(String fileName) {
            try {
                loadProperties();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    

In the second step, I hardcoded a 5-second (5,000 milliseconds) monitoring interval. Of course, you can remove this restriction by adding the monitoring interval as a property to the properties file. For an example of how to do this, see the source code's Example class.

Monitor your files

Now you know how to use a simple file monitor to reload properties at runtime without restarting your application. Since this file monitor is generic, you can use it in other areas. For example, you can use the file monitor with a class loader to implement a hot deployment feature for your application.

Xiaodong Zhang is a senior software consultant. His interests include Java, distributed computing, and software engineering practices.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies