Specification: Configuration for Microprofile Version: 1.2.1-RC1 Status: Draft Release: January 31, 2018 Copyright (c) 2016-2017 Contributors to the Eclipse Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Architecture
This specification defines an easy to use and flexible system for application configuration. It also defines ways to extend the configuration mechanism itself via a SPI (Service Provider Interface) in a portable fashion.
Rationale
Released binaries often contain functionality which need to behave slightly differently depending on the deployment. This might be different REST endpoints to talk to (e.g. depending on the customer for whom a WAR is deployed). Or it might even be whole features which need to be switched on and off depending on the installation. All this must be possible without the need to re-package the whole application binary.
Microprofile-Config provides a way to achieve this goal by aggregating configuration from many different ConfigSources and presents a single merged view to the user. This allows the application to bundle default configuration within the application. It also allows to override the defaults from outside, e.g. via an environment variable a Java system property or via Docker. Microprofile-Config also allows to implement and register own configuration sources in a portable way, e.g. for reading configuration values from a shared database in an application cluster.
Internally, the core Microprofile-Config mechanism is purely String/String based. Type-safety is only provided on top of that by using the proper Converters before handing the value out to the caller.
The configuration key might use dot-separated to prevent name conflicts. This is similar to Java package namespacing:
com.acme.myproject.someserver.url = http://some.server/some/endpoint
com.acme.myproject.someserver.port = 9085
com.acme.myproject.someserver.active = true
com.acme.other.stuff.name = Karl
com.acme.myproject.notify.onerror=karl@mycompany,sue@mcompany
some.library.own.config=some value
Configuration Usage Examples
A configuration object can be obtained programmatically via the ConfigProvider
or automatically via @Inject Config
. An application can then access its configured values via a Config
instance.
Simple Programmatic Example
public class ConfigUsageSample {
public void useTheConfig() {
// get access to the Config instance
Config config = ConfigProvider.getConfig();
String serverUrl = config.getValue("acme.myprj.some.url", String.class);
callToServer(serverUrl);
}
}
If you need to access a different server then you can e.g. change the configuration via a -D
system property:
$> java -jar some.jar -Dacme.myprj.some.url=http://other.server/other/endpoint
Note that this is only one example how to possibly configure your application. Another example is to register Custom ConfigSources to e.g. pick up values from a database table, etc.
If a config value is a ,
separated string, this value can be automatically converted to a multiple element array with \
as the escape character.
When specifying the property myPets=dog,cat,dog\\,cat
in a config source, the following code snippet can be used to obtain an array.
String[] myPets = config.getValue("myPets", String[].class); //myPets = {"dog", "cat", "dog,cat"}
Simple Dependency Injection Example
Microprofile-Config also provides ways to inject configured values into your beans using the @Inject
and the @ConfigProperty
qualifier.
@ApplicationScoped
public class InjectedConfigUsageSample {
@Inject
private Config config;
//The property myprj.some.url must exist in one of the configsources, otherwise a
//DeploymentException will be thrown.
@Inject
@ConfigProperty(name="myprj.some.url")
private String someUrl;
//The following code injects an Optional value of myprj.some.port property.
//Contrary to natively injecting the configured value this will not lead to a
//DeploymentException if the configured value is missing.
@Inject
@ConfigProperty(name="myprj.some.port")
private Optional<Integer> somePort;
//Injects a Provider for the value of myprj.some.dynamic.timeout property to
//resolve the property dynamically. Each invocation to Provider#get() will
//resolve the latest value from underlying Config.
//The existence of configured values will get checked during startup.
//Instances of Provider<T> are guaranteed to be Serializable.
@Inject
@ConfigProperty(name="myprj.some.dynamic.timeout", defaultValue="100")
private javax.inject.Provider<Long> timeout;
//The following code injects an Array, List or Set for the `myPets` property,
//where its value is a comma separated value ( myPets=dog,cat,dog\\,cat)
@Inject @ConfigProperty(name="myPets") private String[] myArrayPets;
@Inject @ConfigProperty(name="myPets") private List<String> myListPets;
@Inject @ConfigProperty(name="myPets") private Set<String> mySetPets;
}
Accessing or Creating a certain Configuration
For using Microprofile-Config in a programmatic way the ConfigProvider
class is the central point to access a configuration.
It allows access to different configurations (represented by a Config
instance) based on the application in which it is used.
The ConfigProvider
internally delegates through to the ConfigProviderResolver
which contains more low-level functionality.
There are 4 different ways to create an Config
instance:
-
In CDI managed components a user can use
@Inject
to access the current application configuration. The default and the auto discovered ConfigSources will be gathered to form a configuration. -
A factory method
ConfigProvider#getConfig()
to create aConfig
object based on automatically picked upConfigSources
of the Application identified by the current Thread Context ClassLoader classpath. Subsequent calls to this method for a certain Application will return the sameConfig
instance. -
A factory method
ConfigProvider#getConfig(ClassLoader forClassLoader)
to create aConfig
object based on automatically picked upConfigSources
of the Application identified by the given ClassLoader. This can be used if the Thread Context ClassLoader does not represent the correct layer. E.g. if you need the Config for a class in a shared EAR lib folder. Subsequent calls to this method for a certain Application will return the sameConfig
instance. -
A factory method
ConfigProviderResolver#getBuilder()
to create aConfigBuilder
object. The builder has no config sources but with only the default converters added. TheConfigBuilder
object can be filled manually viaConfigBuilder#withSources(ConfigSources… sources)
. This configuration instance will by default not be shared by theConfigProvider
. This method is intended be used if a IoC container or any other external Factory can be used to give access to a manually created sharedConfig
.
The Config
object created via builder pattern can be managed as follows:
-
A factory method
ConfigProviderResolver#registerConfig(Config config, ClassLoader classloader)
can be used to register aConfig
within the application. This configuration instance will be shared byConfigProvider#getConfig()
. Any subsequent call toConfigProvider#getConfig()
will return the registeredConfig
instance for this application. -
A factory method
ConfigProviderResolver#releaseConfig(Config config)
to release theConfig
instance. This will unbind the currentConfig
from the application. The ConfigSources that implement thejava.io.Closeable
interface will be properly destroyed. The Converters that implement thejava.io.Closeable
interface will be properly destroyed. Any subsequent call toConfigProvider#getConfig()
orConfigProvider#getConfig(ClassLoader forClassLoader)
will result in a newConfig
instance.
All methods in the ConfigProvider
, ConfigProviderResolver
and Config
implementations are thread safe and reentrant.
The Config
instances created via CDI are Serializable
.
If a Config
instance is created via @Inject Config
or ConfigProvider#getConfig()
or via the builder pattern but later called ConfigProviderResolver#registerConfig(Config config, Classloader classloader)
, the Config
instance will be released when the application is closed.
ConfigSources
A ConfigSource
is exactly what its name says: a source for configured values.
The Config
uses all configured implementations of ConfigSource
to look up the property in question.
ConfigSource Ordering
Each ConfigSource
has a specified ordinal
, which is used to determine the importance of the values taken from the associated ConfigSource
.
A higher ordinal
means that the values taken from this ConfigSource
will override values from lower-priority ConfigSources.
This allows a configuration to be customized from outside a binary, assuming that external ConfigSource
s have higher ordinal
values than the ones whose values originate within the release binaries.
It can also be used to implement a drop-in configuration approach.
Simply create a jar containing a ConfigSource
with a higher ordinal and override configuration values in it.
If the jar is present on the classpath then it will override configuration values from ConfigSources with lower ordinal
values.
config_ordinal = 120
com.acme.myproject.someserver.url = http://more_important.server/some/endpoint
Note that config_ordinal
can be set within any ConfigSource
implementation. The default implementation of getOrdinal()
will attempt to read this value, if found and a valid integer, the value will be used, otherwise the default of 100
will be returned.
Default ConfigSources
A Microprofile-Config implementation must provide ConfigSources for the following data out of the box:
-
System properties (default ordinal=400).
-
Environment variables (default ordinal=300).
-
A
ConfigSource
for each property fileMETA-INF/microprofile-config.properties
found on the classpath. (default ordinal = 100).
Custom ConfigSources
ConfigSources are discovered using the java.util.ServiceLoader
mechanism.
To add a custom ConfigSource
, implement the interface org.eclipse.microprofile.config.spi.ConfigSource
.
public class CustomDbConfigSource implements ConfigSource {
@Override
public int getOrdinal() {
return 112;
}
@Override
public Set<String> getPropertyNames() {
return readPropertyNames();
}
@Override
public Map<String, String> getProperties() {
return readPropertiesFromDb();
}
@Override
public String getValue(String key) {
return readPropertyFromDb(key);
}
@Override
public String getName() {
return "customDbConfig";
}
}
Then register your implementation in a resource file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
by including the fully-qualified class name of the custom implementation in the file.
Custom ConfigSources via ConfigSourceProvider
If you need dynamic ConfigSources you can also register a ConfigSourceProvider
in a similar manner.
This is useful if you need to dynamically pick up multiple ConfigSources of the same kind;
for example, to pick up all myproject.properties
resources from all the JARs in your classpath.
A custom ConfigSourceProvider
must implement the interface org.eclipse.microprofile.config.spi.ConfigSourceProvider
.
Register your implementation in a resource file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider
by including the fully-qualified class name of the custom implementation/s in the file.
An example which registers all YAML files with the name exampleconfig.yaml
:
public class ExampleYamlConfigSourceProvider
implements org.eclipse.microprofile.config.spi.ConfigSourceProvider {
@Override
public List<ConfigSource> getConfigSources(ClassLoader forClassLoader) {
List<ConfigSource> configSources = new ArrayList<>();
Enumeration<URL> yamlFiles
= forClassLoader.getResources("sampleconfig.yaml");
while (yamlFiles.hasMoreElements()) {
configSources.add(new SampleYamlConfigSource(yamlFiles.nextElement()));
}
return configSources;
}
}
Please note that a single ConfigSource
should be either registered directly or via a ConfigSourceProvider
, but never both ways.
Converter
For providing typeseafe configuration we need to convert from the configured Strings into target types.
This happens by providing Converter
s in the Config
.
Built-in Converters
The following Converter
s are provided by Microprofile-Config by default:
-
boolean
andBoolean
, values fortrue
(case insensitive) "true", "1", "YES", "Y" "ON". Any other value will be interpreted asfalse
-
int
andInteger
-
long
andLong
-
float
andFloat
, a dot '.' is used to separate the fractional digits -
double
andDouble
, a dot '.' is used to separate the fractional digits -
Duration
-
LocalTime
-
LocalDate
-
LocalDateTime
-
OffsetDateTime
-
OffsetTime
-
Instant
-
URL
-
Class
based on the result ofClass.forName
All built-in Converter
have the @Priority
of 1
.
Adding custom Converters
A custom Converter
must implement the generic interface org.eclipse.microprofile.config.spi.Converter
.
The Type parameter of the interface is the target type the String is converted to.
You have to register your implementation in a file /META-INF/services/org.eclipse.microprofile.config.spi.Converter
with the fully qualified class name of the custom implementation.
A custom Converter
can define a priority with the @javax.annotation.Priority
annotation.
If a Priority annotation isn’t applied, a default priority of 100 is assumed.
The Config
will use the Converter
with the highest Priority
for each target type.
A custom Converter
for a target type of any of the built-in Converters will overwrite the default Converter.
Converters can be added to the ConfigBuilder
programmatically via ConfigBuilder#withConverters(Converter<?>… converters)
where the type of the converters can be obtained via reflection. However, this is not possible for a lambda converter. In this case, use the method ConfigBuilder#withConverter(Class<T> type, int priority, Converter<T> converter)
.
Array Converters
For the built-in converters and custom converters, the corresponding Array converters are provided by default. The delimiter for the config value is ",". The escape character is "\".
e.g. With this config myPets=dog,cat,dog\\,cat
, the values as an array will be
{"dog", "cat", "dog,cat"}
.
-
Programmatic lookup An array as a class is supported in the programmatic lookup.
private String[] myPets = config.getValue("myPets", String[].class);
myPets will be "dog", "cat", "dog,cat" as an array
-
Injection model For the property injection, Array, List and Set should be supported.
@Inject @ConfigProperty(name="myPets") private String[] myArrayPets; @Inject @ConfigProperty(name="myPets") private List<String> myListPets; @Inject @ConfigProperty(name="myPets") private Set<String> mySetPets;
myPets will be "dog", "cat", "dog,cat" as an array, List or Set.
Automatic Converters
If no built-in nor custom Converter
for a requested Type T
, an implicit Converter is automatically provided if the following conditions are met:
-
The target type
T
has a public Constructor with a String parameter, or -
the target type
T
has apublic static T valueOf(String)
method, or -
the target type
T
has apublic static T parse(CharSequence)
method
Release Notes for MicroProfile Config 1.1
The following changes occurred in the 1.1 release, compared to 1.0
A full list of changes may be found on the MicroProfile Config 1.1 Milestone
API/SPI Changes
-
The
ConfigSource
SPI has been extended with a default method that returns the property names for a givenConfigSource
(#178)
Functional Changes
-
Implementations must now include a
URL
Converter, of@Priority(1)
(#181) -
The format of the default property name for an injection point using
@ConfigProperty
has been changed to no longer lower case the first letter of the class. Implementations may still support this behavior. Instead, MicroProfile Config 1.1 requires the actual class name to be used. (#233) -
Implementations must now support primitive types, in addition to the already specified primtive type wrappers (#204)
Specification Changes
-
Clarified what it means for a value to be present (#216)
Release Notes for MicroProfile Config 1.2
The following changes occurred in the 1.2 release, compared to 1.1
A full list of changes may be found on the MicroProfile Config 1.2 Milestone
API/SPI Changes
-
The
ConfigBuilder
SPI has been extended with a method that allows for a converter with the specified class type to be registered (#205). This change removes the limitation, which was unable to add a lambda converter, from the previous releases.
Functional Changes
-
Implementations must now support the array converter (#259). For the array converter, the programmatic lookup of a property (e.g.
config.getValue(myProp, String[].class)
) must support the return type of the array. For the injection lookup, an Array, List or Set must be supported as well (e.g.@Inject @ConfigProperty(name="myProp") private List<String> propValue;
). -
Implementations must also support the common sense converters (#269) where there is no corresponding type of converters provided for a given class. The implementation must use the class’s constructor with a single string parameter, then try
valueOf(String)
followed byparse(CharSequence)
. -
Implementations must also support Class converter (#267)