This month, I'll finish up discussing configuration issues by showing you a type-safe, properties-file-based configuration system that's easily customized to your own needs.
- The People Problem: Cyber Threats Aren't Just a Technology Challenge
- Encrypted Traffic Management for Dummies eBook
I'm using all of the principles that I discussed on my first article on the subject Solving the Configuration Problem for Java Apps, the main one being that it should just work. Your code should not be cluttered with repetitive routines to detect or handle common errors.
I'm using an
- The configuration file exists and is readable.
- All expected configuration parameters are actually defined in the file.
- All configuration parameters either have associated values or have a default value defined in the code.
- All values are of the correct type (
, etc. — any type with a
- All values are "valid." Out-of-the box tests verify that a number or date is in range
(actually, any class of object that implements
can be checked for "in range"), or that a given value matches a specified regular expression, but you can also define your own load-time verification semantics.
Type safety is accomplished using Java's reflection APIs, so this month's code also demonstrates how to use reflection (at least in a small way).
If you haven't read my two prior articles on this topic: I discussed the basics of enum-based configuration in the first installment of this series, so you'll need to read that article before this one.
Also, I use the
Configuration Without The Pain
Let's start by looking at how you use the system.
Here's a sample properties-based configuration file (which I called
test.properties in my test code).
key.string=Doo What Ditty key.int=10 key.long=9223372036854775806 key.double=1.23 key.boolean=false key.location=/tmp/foo key.url=http://www.holub.com/index.html intBound=2 phoneNumber=(510)555-1212 intNoDefault=0 strNoDefault=????
You can read these properties simply by using an
You just access the value with a single like of code; like this:
Integer i = TestConfiguration.key_int.intValue();
which evaluates to an
URL u = TestConfiguration.key_url.URLValue()
evaluates to a
The system automatically checks that the values specified in the
key=value string are reasonable,
given the variable types.
Some of the values in my test file have been constrained, and a
value ≤ 3,
so if the input file has the line:
All values in the file are typed,
and fetched values are always returned as a typed object. (There is a
Integer i = TestConfiguration. key_int. intValue(); Long l = TestConfiguration. key_long. longValue(); Double d = TestConfiguration. key_double. doubleValue(); File f = TestConfiguration. key_location. fileValue(); URL u = TestConfiguration. key_url. URLValue(); String s = TestConfiguration. key_string. stringValue(); Boolean b = TestConfiguration. key_boolean. booleanValue(); String n = TestConfiguration. phoneNumber. stringValue();
All data validation is done for you automatically, and default values are automatically supplied, so you can assume that the returned value is valid. No extra checking is required and no unexpected exceptions are thrown. Also, Autoboxing (automatic conversion to and from basic types) is your friend. The following works fine:
int i = TestConfiguration. key_int. intValue(); long l = TestConfiguration. key_long. longValue(); double d = TestConfiguration. key_double. doubleValue(); boolean b = TestConfiguration. key_boolean.booleanValue();
You will get a
boolean b = TestConfiguration.key_int.intValue(); // int not convertible to boolean!
The properties defined in the file can be instances of any class that supports a
Object o = TestConfiguration.defaultEverything.value();
You'll have to cast the returned reference to it's actual type, of course.
There's no need to load the properties file explicitly. You just access the element when you need it — aforementioned example code demonstrates literally everything you need to do to access a given property. The file is loaded automatically the first time any of the elements are used, and the loaded values are cached for subsequent use. (The file is read only once.)
However, if any of the verification tests I mentioned earlier fail,
the first access of any element throws a
and catch the exception there.
I usually don't bother to catch the exception, because I pretty-much always want the
So that's it. Pretty painless. We need to do considerable work under the covers to make all this magic possible, of course, but that's where that work belongs — where we don't have to look at it. The basic principle is that you can minimize repetitive work like error checking by moving that work into the class that holds the data.