Dead simple Java application configuration

Dead simple Java application configuration

For my projects I usually do not need a full blown technology stack at hands, so I pick just those libraries which I actually need for them. Furthermore, I do not like to infiltrate my code with annotations, heavily binding it to some specific framework. Moreover, I like my projects to stay slim so them can be launched as command line tools without the framework or application server overhead slowing things down.

So when I was looking for an alternative to Spring Boot’s configuration mechanism, I was out of luck to find any which would fit my needs. Therefore I created the refcodes-configuration (Javadoc) artifact, just doing the configuration job in a slim non intrusive way.

Getting started

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1 <dependencies>
2   ...
3   <dependency>
4     <artifactId>refcodes-configuration</artifactId>
5     <groupId>org.refcodes</groupId>
6     <version>1.1.9</version>
7   </dependency>
8   ...
9 </dependencies>

Some basic example, loading and storing properties

A very (very!) basic example may look as below, just loading some Java based properties file.

 1 import static org.refcodes.configuration.PropertiesSugar.*;
 2 // ...
 3 public class Foo {
 4   public Foo() throws IOException, ParseException {
 5     ResourcePropertiesBuilder properties = seekFromJavaProperties( "application.properties" );
 6     String message = properties.get( "message" );
 7     // ...
 8     properties.put( "message", "Hello world!" );
 9     properties.flush();
10   }
11   // ...
12 }

Right now, besides Java based properties, you may also use TOML, XML, YAML or JSON based properties files. For example, instead of seekFromJavaProperties you may use seekFromTomlProperties instead to load TOML based properties.

The above example made you load properties from a file called “application.properties” (place it in your project’s /target folder, other locations are also possible) and access a property with name “message”. After altering the property “message”, you flush the properties back to the file.

Take a look at the various loadFrom and seekFrom flavors you may also want to use. Look at the PropertiesSugar class for the according syntactic sugar.

Below see an example on how to load the properties and create a configuration class’ instance from them properties:

 1 import static org.refcodes.configuration.PropertiesSugar.*;
 2 // ...
 3 public class Foo {
 4   public Foo() throws IOException, ParseException {
 5     ResourcePropertiesBuilder properties = seekFromJavaProperties("application.properties" );
 6     FooConfig config = properties.toType( FooConfig.class );
 7     // ...
 8     properties.put( "message", "Hello world!" );
 9     properties.saveTo("/some/path/to/save/to");
10   }
11 
12   public static class FooConfig {
13     String message;
14     int frequency;
15     TemperatureUnit unit;
16     int port;
17   }
18   // ...
19 }

Instead of doing a flush of the properties back to the file, you now do a saveTo of the properties to some other location.

Take a look at the various fileTo and saveTo flavors you may also want to use. Look at the PropertiesSugar class for the according syntactic sugar.

System properties, environment variables and profiles

The above example was straight forward and very plain. Now let’s see on how to also get your properties enriched with additional properties sources such as Java ‘s system properties or the operating system’s environment variables having some defined precedence on which properties source rules out which other properties source.

 1 import static org.refcodes.configuration.PropertiesSugar.*;
 2 // ...
 3 public class Foo {
 4   // ...
 5   public Foo() throws IOException, ParseException {
 6     Properties properties = fromProfile(
 7       toPrecedence(
 8         fromSystemProperties(), fromEnvironmentVariables(), seekFromJavaProperties( "application.properties" )
 9       )
10     );
11     // ...
12   }
13   // ..
14 }

Above, the system properties overrule the operating system’s environment variables which overrule the Java based properties. Finally we create a profile from the resulting properties:

Properties with precedence and profile

Under the hood, each property key represents a path. We may create hierarchies of properties (before we omitted the prepending “/” which is optional but which will be automatically added within the properties object):

1 /message=Hello world!
2 /test/message=Hello test!
3 /prod/message=Hello production!
4 /int/message=Hello integration!

When we add a profile declaration pointing to test, we make the property below the path test/message overrule the property message. To do so, we add a /runtime/profiles property:

1 /runtime/profiles=test
2 /message=Hello world!
3 /test/message=Hello test!
4 /prod/message=Hello production!
5 /int/message=Hello integration!

Now retrieving the message property using the above properties from the above created properties object will result in “Hello test!” to be retrieved.

Observe and schedule, an advanced example

For the advance example to pull the extended functionality, include the following dependency (without the three dots “…”) in your pom.xml:

1 <dependencies>
2   ...
3   <dependency>
4     <artifactId>refcodes-configuration-ext-observer</artifactId>
5     <groupId>org.refcodes</groupId>
6     <version>1.1.9</version>
7   </dependency>
8   ...
9 </dependencies>

To observe the properties for changes on the file system, we encapsulate our properties with an observable decorator and a scheduled decorator. On the observable we register a PropertiesObserver to listen for subtypes of the PropertyEvent type:

 1 import static org.refcodes.configuration.ext.observer.ObservablePropertiesSugar.*;
 2 // ...
 3 public class Foo {
 4   // ...
 5   public Foo() throws IOException, ParseException {
 6     ResourcePropertiesBuilder properties = seekFromJavaProperties( ("application.properties") );
 7     ObservableResourcePropertiesBuilder observable = observe( properties );
 8     observable.subscribeObserver( event -> {
 9       // Do something here upon property change...
10     } );
11     schedule( observable, 3000, ReloadMode.ORPHAN_REMOVAL );
12   }
13   // ..
14 }

We encapsulated our properties with an observable and then encapsulated them with a scheduler. The resulting properties object behaves just like the Properties type we encapsulated in the first place. You may listen for create, update or delete actions (as of the PropertyAction).

Observable scheduled properties

Take a look at the observe method you may also want to use. Look at the ObservablePropertiesSugar class for the according syntactic sugar.

Further reading

For examples and usage, please take a look at the according Unit-Tests here and for the advanced part lease take a look at the according Unit-Tests here.

comments powered by Disqus