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.
More Insights
White Papers
More >>Reports
- Strategy: 3 Steps to a Hands-Free Cloud
- Strategy: Smartphone Smackdown: Galaxy Note II vs. Lumia 920 vs. iPhone 5
Webcasts
More >>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.
In particular,
I'm using an enum
- 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 (
,Integer ,Long ,File ,URL , etc. — any type with aDate constructor works).String - 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.Comparable
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 ExtendedLoggerExtendedLoggerprintf()
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 enumTestConfiguration
You just access the value with a single like of code; like this:
Integer i = TestConfiguration.key_int.intValue();
which evaluates to an Integerkey.int=10
in test.properties. Similarly,
URL u = TestConfiguration.key_url.URLValue()
evaluates to a URL"http://www.holub.com/index.html"
The system automatically checks that the values specified in the key=value string are reasonable,
given the variable types.
For example, key.intkey.urlphoneNumber
Some of the values in my test file have been constrained, and a ConfigurationErrorintBoundvalue ≤ 3,
so if the input file has the line:
intBound=4
then a ConfigurationError
All values in the file are typed,
and fetched values are always returned as a typed object. (There is a StringClassCastException
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 ClassCastException
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 Stringvalue()
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 ConfigurationErrorConfigurationErrorjava.lang.Error
TestConfiguration.load();
and catch the exception there.
Nonetheless,
I usually don't bother to catch the exception, because I pretty-much always want the
ConfigurationErrorload()main()ServletContextListenerload()
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.


