1. The Tamaya Injection API
1.1. Overview
Inversion of Control (aka IoC/the Hollywood Principle) has proven to be very useful and effective in avoiding boilerplate code. In Java there are different frameworks available that all provide IoC mechanisms. Unfortunately IoC is not a built-in language feature. So for a portable solution that works also in Java SE Tamaya itself has to provide the according injection services. This module adds this functionality to Tamaya. As an example refer to the following code snippet:
package foo.bar;
public class ConfiguredClass{
// resolved by default, using property name, class and package name: foo.bar.ConfiguredClass.testProperty
private String testProperty;
@ConfiguredProperty(keys={"a.b.c.key1","a.b.legacyKey",area1.key2"})
@DefaultValue("The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.")
String value1;
// Using a (default) String -> Integer converter
@ConfiguredProperty(keys="a.b.c.key2")
private int value2;
// resolved by default as foo.bar.ConfiguredClass.accessUrl
// Using a (default) String -> URL converter
@DefaultValue("http://127.0.0.1:8080/res/api/v1/info.json")
private URL accessUrl;
// Config injection disabled for this property
@NoConfig
private Integer int1;
// Overriding the String -> BigDecimal converter with a custom implementation.
@ConfiguredProperty(keys="BD")
@WithAdapter(MyBigDecimalRoundingAdapter.class)
private BigDecimal bigNumber;
...
}
The class does not show all (but most) possibilities provided. Configuring an instance of the
class using Tamaya is very simple. The only thing is to pass the instance to Tamaya to let
Tamaya inject the configuration (or throw a ConfigException
, if this is not possible):
ConfiguredClass
InstanceConfiguredClass classInstance = new ConfiguredClass();
ConfigurationInjector.configure(configuredClass);
1.1.1. The Annotations in detail
The ConfigurationInjector
interface provides methods that allow any kind of instances to be configured
by passing the instances to T ConfigurationInjector.getInstance().configure(T);
. The classes passed
hereby must not be annotated with @ConfiguredProperty
for being configurable. By default Tamaya
tries to determine configuration for each property of an instance passed, using the following resolution policy:
Given a class a.b.MyClass
and a field myField
it would try to look up the following keys:
a.b.MyClass.myField
a.b.MyClass.my-field
MyClass.myField
MyClass.my-field
myField
my-field
So given the following properties:
a.b.Tenant.id=1234
Tenant.description=Any kind of tenant.
name=<unnamed>
The following bean can be configured as follows:
package a.b;
public final class Tenant{
private int id;
private String name;
private String description;
public int getId(){
return id;
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
}
Tenant tenant = ConfigurationInjector.getInstance().configure(new Tenant());
In many cases you want to create a supplier that simply creates instances that are correctly configured as defined
by the current context. This can be done using Suppliers
:
Supplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier(Tenant::new);
Hereby this annotation can be
used in multiple ways and combined with other annotations such as @DefaultValue
,
@WithLoadPolicy
, @WithConfig
, @WithConfigOperator
, @WithPropertyAdapter
.
To illustrate the mechanism below the most simple variant of a configured class is given:
pubic class ConfiguredItem{
@ConfiguredProperty
private String aValue;
}
When this class is configured, e.g. by passing it to Configuration.configure(Object)
,
the following is happening:
-
The current valid
Configuration
is evaluated by callingConfiguration cfg = Configuration.of();
-
The current property value (String) is evaluated by calling
cfg.get("aValue");
-
if not successful, an error is thrown (
ConfigException
) -
On success, since no type conversion is involved, the value is injected.
-
The configured bean is registered as a weak change listener in the config system’s underlying configuration, so future config changes can be propagated (controllable by applying the
@WithLoadPolicy
annotation).
In the next example we explicitly define the property value:
pubic class ConfiguredItem{
@ConfiguredProperty
@ConfiguredProperty("a.b.value")
@configuredProperty("a.b.deprecated.value")
@DefaultValue("${env:java.version}")
private String aValue;
}
Within this example we evaluate multiple possible keys. Evaluation is aborted if a key could be successfully
resolved. Hereby the ordering of the annotations define the ordering of resolution, so in the example above
resolution equals to "aValue", "a.b.value", "a.b.deprecated.value"
. If no value could be read
from the configuration, it uses the value from the @DefaultValue
annotation. Interesting here
is that this value is not static, it is evaluated by calling Configuration.evaluateValue(Configuration, String)
.