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-properties
(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
2
3
4
5
6
7
8
9
<dependencies>
...
<dependency>
<artifactId>refcodes-properties</artifactId>
<groupId>org.refcodes</groupId>
<version>3.3.8</version>
</dependency>
...
</dependencies>
Usage scenarios
This section discusses some fundamental usage scenarios such loading and retrieving properties, properties profiles and watching (observing) persisted properties for external changes. Obfuscating persisted properties is discussed in the article Automatically obfuscate your Java application’s configuration.
Loading and storing properties
A very (very!) basic example may look as below, just loading some Java based properties file.
1
2
3
4
5
6
7
8
9
10
11
12
import static org.refcodes.properties.PropertiesSugar.*;
// ...
public class Foo {
public Foo() throws IOException, ParseException {
ResourcePropertiesBuilder properties = seekFromJavaProperties( "application.properties" );
String message = properties.get( "message" );
// ...
properties.put( "message", "Hello world!" );
properties.flush();
}
// ...
}
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
andseekFrom
flavors you may also want to use. Look at thePropertiesSugar
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import static org.refcodes.properties.PropertiesSugar.*;
// ...
public class Foo {
public Foo() throws IOException, ParseException {
ResourcePropertiesBuilder properties = seekFromJavaProperties("application.properties" );
FooConfig config = properties.toType( FooConfig.class );
// ...
properties.put( "message", "Hello world!" );
properties.saveTo("/some/path/to/save/to");
}
public static class FooConfig {
String message;
int frequency;
TemperatureUnit unit;
int port;
}
// ...
}
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
andsaveTo
flavors you may also want to use. Look at thePropertiesSugar
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
2
3
4
5
6
7
8
9
10
11
12
13
14
import static org.refcodes.properties.PropertiesSugar.*;
// ...
public class Foo {
// ...
public Foo() throws IOException, ParseException {
Properties properties = fromProfile(
toPrecedence(
fromSystemProperties(), fromEnvironmentVariables(), seekFromJavaProperties( "application.properties" )
)
);
// ...
}
// ..
}
Above, the system properties
overrule the operating system
’s environment variables
which overrule the Java based properties (see the ConfigLocator on how the properties file is discovered and where it is to be placed). Finally we create a profile
from the resulting properties:
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
2
3
4
/message=Hello world!
/test/message=Hello test!
/prod/message=Hello production!
/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
2
3
4
5
/runtime/profiles=test
/message=Hello world!
/test/message=Hello test!
/prod/message=Hello production!
/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
2
3
4
5
6
7
8
9
<dependencies>
...
<dependency>
<artifactId>refcodes-properties-ext-observer</artifactId>
<groupId>org.refcodes</groupId>
<version>3.3.8</version>
</dependency>
...
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
import static org.refcodes.properties.ext.observer.ObservablePropertiesSugar.*;
// ...
public class Foo {
// ...
public Foo() throws IOException, ParseException {
ResourcePropertiesBuilder properties = seekFromJavaProperties( "application.properties" );
ObservableResourcePropertiesBuilder observable = observe( properties );
observable.subscribeObserver( event -> {
// Do something here upon property change...
} );
schedule( observable, 3000, ReloadMode.ORPHAN_REMOVAL );
}
// ..
}
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
).
Take a look at the
observe
method you may also want to use. Look at theObservablePropertiesSugar
class for the according syntactic sugar.
Further reading
See the blog post on the refcodes-properties
artifact for further details on the usage of this artifact. Also see the blog posts Dead simple Java application configuration, Automatically obfuscate your Java application’s configuration and All-in-one Java configuration properties at hand. For examples and usage, please take a look at the according Unit-Tests
here and for the advanced part please take a look at the according Unit-Tests
here.