Avaje Config
avaje-config provides external configuration for JVM apps. We can provide configuration via yaml or properties files and specify which files to load using command line argument and resources.
License | Source | API Docs | Issues | Releases |
---|---|---|---|---|
Apache2 | Github | Javadoc | Github |
Dependency
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>${config-version}</version>
</dependency>
Expression evaluation
Expressions start with ${
end with }
. They can optionally
define a default value using a :
as we see in the example below for the
username and password which have default values of mydb
and notSecure respectively.
Expressions are evaluated using environment variables, system properties as well as other property files.
example
app.name: myapp
images.home: ${user.home}/myapp/images
datasource:
db:
username: ${DB_USER:mydb}
password: ${DB_PASS:notSecure}
url: ${DB_URL}
Startup
avaje-config will initialize and load configuration when it is first used.
initialization
- Loads all the configuration files
- Performs expression evaluation on all the values
- Using ServiceLoader finds and loads all ConfigurationSource plugins
- Checks
config.load.systemProperties
and loads into system properties if set - Checks
config.watch.enabled
and starts file watching if set
config.load.systemProperties
If we set config.load.systemProperties
to true, all loaded properties are then set into the system properties.
You can provide an additional property system.excluded.properties
to provide a list of configuration properties you want to exclude from being loaded into System Properties.
Loading configuration
avaje-config provides ways to load external configuration for running an application and running tests. It also provides a simple mechanism to make it easy to run the application locally (typically in order to run integration tests locally).
Running app
There are five main ways to provide configuration used for the purpose of running the application.
- 1. Default values via
src/main/resources
- 2. Command line arguments
- 3. avaje.profiles
- 4. load.properties
- 5. ConfigurationSource plugins
1. Default values via main resources
We can provide "default" configuration values by putting into src/main/resources
a application.yaml
or application.properties
file.
These values act as default values and can be overwritten by configuration
provided by other sources such as files specified by command line arguments/profiles/load.properties
.
2. Command line arguments
We can specify external configuration files to use via command line arguments.
Arguments proceeded by -P
are considered possible configuration files
and avaje-config will try to load them. If they are not valid files that exist
then they are ignored.
example
java -jar myapp.jar -P/etc/config/myapp.properties -P/etc/other.yaml
In the example above two external files are configuration files that are loaded providing configuration values for the application.
3. avaje.profiles
Setting a avaje.profiles
property or AVAJE_PROFILES
environment variable will cause avaje config to load the property files in the form application-<profile>.properties
(will also work for yml/yaml files).
For example, if you set the avaje.profiles property (perhaps via command line) to dev,docker
it will attempt to load application-dev.properties
and application-docker.properties
.
4. load.properties
We can specify a load.properties
property to define additional configuration
files to load.
example in application.yaml
## we don't need to specify path if it's in src/main/resources
## can be combined with eval to have something like feature flags
load.properties: ${ENV:local}.properties /etc/other.yaml
After default configuration files are loaded, the load.properties property is read and if specified these configuration files are loaded.
load.properties
is pretty versatile and can even be chained.
For example, in using the above properties, you can load based on the ENV
environment variable/-Dprofile
JVM arg, and in the loaded property file you can add load.properties
to load even more properties and so on.
5. ConfigurationSource
We can also use plugins that implement ConfigurationSource to load configuration from other sources. Refer to the ConfigurationSource section for more details.
Running Tests
To provide configuration for running tests, add into src/test/resources
either
application-test.yaml
or application-test.properties
.
The configuration from these files will be loaded and used when running tests. This configuration
will override any configuration supplied by files in src/main/resources
.
Running locally
When we run our application locally, we can provide configuration for this
via properties or yaml files in a ~/.localdev
directory.
For example, when running locally we want to provide configuration that configures our application to use a local database. We want to do this when we run integration tests locally running application(s).
app.name
We must specify a app.name property to do this.
example
app.name=myapp
Then in ~/.localdev
, put a yaml or properties configuration file
using the app.name. For example, given app.name=myapp
have
either ~/.localdev/myapp.yaml
or ~/.localdev/myapp.properties
define the configuration we want to use when running locally.
File watching
If config.watch.enabled
is set to true, then avaje-config will
watch the config files and reload configuration when the files change.
By default, it will check the config files every 10 seconds. We can change this by setting
config.watch.period
example
config:
watch:
enabled: true
period: 5 ## check every 5 seconds
We can use this as a simple form of feature toggling. For example if we are using kubernetes and have configuration file based on a config map, we can edit the config map to enable/disable features.
config.watch.enabled: true
## some config values to toggle features on/off
feature.cleanup.enabled: true
feature.processEmails.enabled: false
if (Config.enabled("feature.cleanup.enabled") {
...
}
Config
We use Config
to access the application configuration. Config can be used
anywhere in application code - static initializers, enums, constructors etc. There is no limitation
on where we can use Config.
Config
has convenient static methods to access the underlying configuration. It also has
asConfiguration() to return the underlying configuration
and asProperties() to return the loaded configuration as standard
properties.
Get Property
Config
provides method to get property values as String, int, long, boolean,
BigDecimal, Enum, URI and Duration.
Examples
// get a String property
String value = Config.get("myapp.foo");
// with a default value
String value = Config.get("myapp.foo", "withDefaultValue");
int port = Config.getInt("app.port");
long val = Config.getInt("foo", 42);
long port = Config.getLong("app.port");
long val = Config.getLong("foo", 42);
boolean val = Config.getBool("feature.cleanup");
boolean val = Config.getBool("feature.cleanup", true);
// Config.enabled() is an alias for Config.getBool()
boolean val = Config.enabled("feature.cleanup");
boolean val = Config.enabled("feature.cleanup", true);
BigDecimal val = Config.getDecimal("myapp.tax.rate");
BigDecimal val = Config.getDecimal("myapp.tax.rate", "12.5");
MyEnum val = Config.getEnum(MyEnum.class, "myapp.code");
MyEnum val = Config.getEnum(MyEnum.class, "myapp.code", MyEnum.DEFAULT);
URI val = Config.getURI("other.url");
URI val = Config.getURI("other.url", "https://other:8090");
List and Set
We can also return configuration values as a List/Set of String, int, long.
List<Integer> codes = Config.list().ofInt("my.codes", 42, 54);
Set<String> operations = Config.set().of("my.operations", "put", "delete");
Function
We can also provide a function to map a config to a type.
CustomObj codes = Config.getAs("my.codes", s -> new CustomObj(s));
Optional<CustomObj> operations = Config.getAsOptional("my.codes", s -> new CustomObj(s));
List<CustomObj> operations = Config.list().ofType("my.codes", s -> new CustomObj(s));
Set Property
Use setProperty()
to set configuration values.
When we set values, this will fire any configuration callback listeners that are registered for the key.
Config.setProperty("other.url", "https://bazz");
Config.setProperty("feature.cleanup", "false");
Event Publishing
Use eventBuilder()
to publish multiple changes at once.
Config.eventBuilder("EventName")
.put("someKey", "val0")
.put("someOther.key", "42")
.remove("foo")
.publish();
On Change
We can register callbacks that execute when a configuration key has it's value changed.
Single property onChange
Config.onChange("other.url", newUrl -> {
// do something interesting with the changed
// config value
...
});
Multi property onChange
Config.onChange(event -> {
Set<String> changedKeys = event.modifiedKeys();
Configuration updatedConfig = event.configuration();
// do something with the changed values.
});
// Filter events for specific properties
Config.onChange(event -> {
Set<String> changedKeys = event.modifiedKeys();
Configuration updatedConfig = event.configuration();
// do something with the changed values.
}, "someKey", "someOther.key");
asProperties()
Obtain all the configuration values as standard properties using Config.asProperties()
Properties properties = Config.asProperties();
asConfiguration()
Obtain the underlying configuration via Config.asConfiguration()
.
Config provides a static singleton scope for the underlying Configuration.
Generally there should not be much need to access the underlying configuration, as the static access is much more convenient.
Configuration configuration = Config.asConfiguration();
forPath()
Obtain a filtered configuration via Config.forpath()
.
//given properties like the below in application.properties
database.example.password=secretPassword
database.example.username=notSecretUsername
Configuration dbConfiguration = Config.forPath("database");
Configuration dbExampleConfiguration = dbConfiguration.forPath("example");
// or more simply
Configuration dbExampleConfiguration = Config.forPath("database.example");
// will return secretPassword
dbExampleConfiguration.get("password");
// will return secretPassword
dbExampleConfiguration.get("username");
ConfigurationSource Plugins
Plugins implement the ConfigurationSource
interface and are found
and registered via ServiceLoader
. This means they have a
file at src/main/resources/META-INF/services/io.avaje.config.ConfigExtension
which contains the class name of the implementation.
Plugins implement the method
void load(Configuration configuration);
They are able to use configuration to read properties if needed, for example read the host name of a redis server to read configuration from.
Plugins typically read the configuration after all files have loaded, and then use setProperty(key, value)
to add/modify properties. The values can contain expressions and will have
their expressions evaluated as part of setProperty.
Refer to the example plugin - MyExternalLoader.java
Event Logging
By default, avaje-config
will immediately log initialization events to it's own configured system logger. If you want to use your own configured logger, you can extend the ConfigurationLog
interface and
register via ServiceLoader
. This means you have a
file at src/main/resources/META-INF/services/io.avaje.config.ConfigExtension
which contains the class name of the implementation.
Custom Event loggers implement the methods:
/**
* Log an event with the given level, message, and thrown exception.
*/
void log(Level level, String message, Throwable thrown);
/**
* Log an event with the given level, formatted message, and arguments.
* <p>
* The message format is as per {@link java.text.MessageFormat#format(String, Object...)}.
*/
void log(Level level, String message, Object... args);
Additionally, you can implement the two default methods and provide behavior before and after the configs are loaded.
/**
* Invoked when the configuration is being initialized.
*/
default void preInitialisation() {
}
/**
* Invoked when the initialisation of configuration has been completed.
*/
default void postInitialisation() {
}