| :jbake-type: page |
| :jbake-status: published |
| |
| = Apache Tamaya - Extension: Injection |
| |
| toc::[] |
| |
| |
| [[Injection]] |
| == Tamaya Injection (Extension Module) |
| |
| Tamaya _Injection_ is an extension module. Refer to the link:../extensions.html[extensions documentation] for further details. |
| |
| === What functionality this module provides ? |
| |
| Tamaya _Injection_ provides functionality for injecting configured values into beans, or creating configuration |
| template instances. |
| |
| 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. |
| |
| === Compatibility |
| |
| The module is based on Java 7, so it can be used with Java 7 and beyond. |
| |
| === Installation |
| |
| Basically Tamaya's injection API is deployed as API artifact: |
| |
| [source, xml] |
| ----------------------------------------------- |
| <dependency> |
| <groupId>org.apache.tamaya.ext</groupId> |
| <artifactId>tamaya-injection-api</artifactId> |
| <version>{tamaya_version}</version> |
| </dependency> |
| ----------------------------------------------- |
| |
| To use injection with Java SE you must add the corresponding dependency to your module: |
| |
| [source, xml] |
| ----------------------------------------------- |
| <dependency> |
| <groupId>org.apache.tamaya.ext</groupId> |
| <artifactId>tamaya-injection</artifactId> |
| <version>{tamaya_version}</version> |
| </dependency> |
| ----------------------------------------------- |
| |
| Similarly there are other injection implementations available, targetig platforms such as |
| |
| * link:mod_spring.html[Spring, Spring Boot] |
| * link:mod_CDI.html[Java EE/CDI] |
| |
| |
| === Core Concepts |
| |
| Basically you annotate fields or methods in your beans with +@Config+ to enable configuration injection. Tamaya |
| additionally defines further annotations that allo you to define additional aspects such as default values, custom |
| converters etc. The following example illustrates the basic functionality: |
| |
| [source,java] |
| .Annotated Example Class |
| -------------------------------------------- |
| package foo.bar; |
| |
| public class ConfiguredClass { |
| |
| // resolved by default, using property name, class and package name: foo.bar.ConfiguredClass.testProperty |
| private String testProperty; |
| |
| // Trying to resolve mutiple keys, with a default value, if none could be resolved |
| @Config({"a.b.c.key1","a.b.legacyKey",area1.key2"}, defaultValue="The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.") |
| String value1; |
| |
| // Typical case |
| @Config("a.b.c.key2") |
| private int value2; |
| |
| // resolved by default as foo.bar.ConfiguredClass.accessUrl |
| // Using a (default) String -> URL converter |
| @Config(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. |
| @Config("BD") |
| @WithPropertyConverter(MyBigDecimalRoundingAdapter.class) |
| private BigDecimal bigNumber; |
| |
| ... |
| } |
| -------------------------------------------- |
| |
| |
| When configuring data or configuration classes it is also possible to auto-inject the fields identified. For activating |
| this feature a class must be annotated with +@ConfigAutoInject+: |
| |
| [source, java] |
| .An autoinjected bean class |
| -------------------------------------------- |
| package a.b; |
| |
| @ConfigAutoInject |
| public final class Tenant { |
| private int id; |
| private String name; |
| private String description; |
| @NoConfig // prevents auto injection for this field |
| private String id2; |
| |
| public int getId(){ |
| return id; |
| } |
| public String getName(){ |
| return name; |
| } |
| public String getDescription(){ |
| return description; |
| } |
| } |
| -------------------------------------------- |
| |
| These examples do not show all possibilities provided. Configuring instance of these |
| class using Tamaya is very simple: Just pass the instance to Tamaya to let |
| Tamaya inject the configuration (or throw a +ConfigException+, if this is not possible): |
| |
| [source,java] |
| .Configuring the +ConfiguredClass+ Instance |
| -------------------------------------------- |
| ConfiguredClass classInstance = new ConfiguredClass(); |
| ConfigurationInjector.getInstance().configure(configuredClass); |
| |
| Tenant tenant = new Tenant(); |
| ConfigurationInjector.getInstance().configure(tenant); |
| -------------------------------------------- |
| |
| NOTE: Configuration injection works similarly, when used with other integration modules, e.g. when Tamaya is used |
| with CDI, Spring or within an OSGI container. For further details refer also to the corresponding integration module's |
| documentation. |
| |
| |
| ==== The ConfigurationInjector |
| |
| 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 +@Config+ for being configurable. |
| |
| ==== Accessing Supplier instances |
| |
| 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+: |
| |
| [source, java] |
| -------------------------------------------- |
| Supplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier( |
| new Supplier<Tenant>(){ |
| public Tenant get(){ |
| return new Tenant(); |
| } |
| }); |
| -------------------------------------------- |
| |
| With Java 8 it's even more simple: |
| |
| [source, java] |
| -------------------------------------------- |
| Supplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier( |
| Tenant::new); |
| -------------------------------------------- |
| |
| Hereby this annotation can be used in multiple ways and combined with other annotations such as |
| +@WithLoadPolicy+, +@WithConfigOperator+, +@WithPropertyConverter+. |
| |
| |
| ==== Minimal Example |
| |
| To illustrate the mechanism below the most simple variant of a configured class is given: |
| |
| [source,java] |
| .Most simple configured class |
| -------------------------------------------- |
| pubic class ConfiguredItem{ |
| @Config |
| private String aValue; |
| } |
| -------------------------------------------- |
| |
| When this class is configured, e.g. by passing it to +ConfigurationInjector.getInstance().configure(Object)+, |
| the following is happening: |
| |
| * The current valid +Configuration+ is evaluated by calling +Configuration cfg = Configuration.current();+ |
| * The current property value (String) is evaluated by calling +cfg.get("aValue");+ for each possible key (mutliple |
| keys are possible). |
| * if not successful, an error is thrown (+ConfigException+) |
| * On success, since no type conversion is involved, the value is injected. |
| |
| |
| === The Annotations in detail |
| |
| ==== Using `@Config` |
| |
| This is the main annotation targeting a field in a class for configuration injection. |
| |
| ===== Evaluating of _configuration keys_ |
| |
| 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: |
| |
| [source, listing] |
| -------------------------------------------- |
| a.b.MyClass.myField |
| a.b.MyClass.my-field |
| MyClass.myField |
| MyClass.my-field |
| myField |
| my-field |
| -------------------------------------------- |
| |
| |
| This behaviour can be adapted, e.g. by using the `@ConfigDefaultSections` annotation on the |
| declaring type: |
| |
| -------------------------------------------- |
| @ConfigDefaultSections("a.b.c", "deprecated") |
| pubic class MyClass{ |
| @Config |
| private String myField; |
| } |
| -------------------------------------------- |
| |
| This will result in a modified lookup chain as illustrated below: |
| |
| [source, listing] |
| -------------------------------------------- |
| a.b.c.myField |
| a.b.c.my-field |
| deprecated.myField |
| deprecated.my-field |
| -------------------------------------------- |
| |
| This helps to reduce redundancy when referring to you configuration keys. Additionally |
| it is also possible to define absolute key entries, e.g. |
| |
| -------------------------------------------- |
| @ConfigDefaultSections("a.b.c") |
| pubic class MyClass{ |
| @Config("myField" /* relative */, "[absolute.key]") |
| private String myField; |
| } |
| -------------------------------------------- |
| |
| This will result in a lookup chain as illustrated below: |
| |
| [source, listing] |
| -------------------------------------------- |
| a.b.c.myField |
| absolute.key # default sections are ignored |
| -------------------------------------------- |
| |
| |
| ===== Using defaults |
| |
| In the next example we explicitly define the _default_ property value: |
| [source,java] |
| -------------------------------------------- |
| pubic class ConfiguredItem{ |
| |
| @Config(value={"aValue", "a.b.value","a.b.deprecated.value"}, defaultValue="${env:java.version}") |
| private String aValue; |
| } |
| -------------------------------------------- |
| |
| |
| ==== Automatically inject all items using `@ConfigAutoInject` |
| |
| Using `@ConfigAutoInject` allows you to automatically select all properties found for |
| configuration injection: |
| |
| [source,java] |
| -------------------------------------------- |
| @ConfigAutoInject |
| pubic class ConfiguredItem{ |
| |
| private transient int sum; |
| |
| private String a; |
| private String b; |
| Private String c; |
| } |
| -------------------------------------------- |
| |
| Adding the `@NoConfig` annotation prevents a field or method to be auto-injected from |
| configuration. This is especially useful, if a type is annotated as @ConfigAutoInject with auto-confiuration |
| turned on as follows: |
| |
| [source,java] |
| -------------------------------------------- |
| @NoConfig |
| private transient int sum; |
| -------------------------------------------- |
| |
| In this case the fields +a,b,c+ are configured, whereas the field +sum+ is ignored regarding |
| configuration. |
| |
| |
| ==== Adding custom operators using `@WithConfigOperator` |
| |
| The @WithConfigOperator annotation allows you define a class of type +ConfigOperator+, to being applied |
| to the final +Configuration+, BEFORE the value is injected. This can be used for various use cases, e.g. |
| filtering or validating the visible properties for a certain use case. |
| |
| [source,java] |
| -------------------------------------------- |
| |
| @WithConfigOperator(MyConfigView.class) |
| pubic class ConfiguredItem{ |
| |
| @Config |
| private String a; |
| |
| } |
| -------------------------------------------- |
| |
| |
| ==== Adding custom property converters using `@WithPropertyConverter` |
| |
| The @WithPropertyConverter annotation allows you to define a class of type +PropertyConverter+, to be applied |
| on a property configured to convert the String value to the expected injected type. This can be used for |
| various use cases, e.g. adding custom formats, config models, decryption. |
| |
| [source,java] |
| -------------------------------------------- |
| |
| pubic class ConfiguredItem{ |
| |
| @WithPropertyConverter(MyPropertyConverter.class) |
| @Config |
| private String a; |
| |
| } |
| -------------------------------------------- |
| |
| |
| ==== Inject a `DynamicValue` |
| |
| Within this example we evaluate a dynamic value. This mechanism allows you to listen for configuration changes and to |
| commit new values exactly, when convenient for you. |
| |
| [source,java] |
| -------------------------------------------- |
| pubic class ConfiguredItem{ |
| |
| @Config(value={"aValue", "a.b.value","a.b.deprecated.value"}, defaultValue="${env:java.version}") |
| private DynamicValue aValue; |
| } |
| -------------------------------------------- |
| |
| The +DynamicValue+ provides you the following functionality: |
| |
| [source,java] |
| -------------------------------------------- |
| public interface DynamicValue<T> { |
| |
| T get(); |
| T getNewValue(); |
| T evaluateValue(); |
| T commitAndGet(); |
| void commit(); |
| void discard(); |
| boolean updateValue(); |
| |
| void setUpdatePolicy(UpdatePolicy updatePolicy); |
| UpdatePolicy getUpdatePolicy(); |
| void addListener(PropertyChangeListener l); |
| void removeListener(PropertyChangeListener l); |
| |
| boolean isPresent(); |
| T orElse(T other); |
| T orElseGet(ConfiguredItemSupplier<? extends T> other); |
| <X extends Throwable> T orElseThrow(ConfiguredItemSupplier<? extends X> exceptionSupplier) throws X; |
| |
| } |
| |
| public enum UpdatePolicy{ |
| IMMEDIATE, |
| EXPLICIT, |
| NEVER, |
| LOG_AND_DISCARD |
| } |
| -------------------------------------------- |
| |
| Summarizing +DynamicValue+ looks somehow similar to the new +Optional+ class added with Java 8. It provides |
| a wrapper class around a configured instance. Additionally this class provides functionality that gives |
| active control, to manage a configured value based on a ++LoadingPolicy+: |
| |
| * +IMMEDIATE+ means that when the configuration system detects a change on the underlying value, the new value |
| is automatically applied without any further notice. |
| * +EXPLICIT+ means that a new configuration value is signalled by setting the +newValue+ property. if +getNewValue()+ |
| returns a non null value, the new value can be applied by calling +commit()+. You can always access the newest value, |
| hereby implicitly applying it, by accessing it via +commitAndGet()+. Also it is possible ti ignore a change by calling |
| +discard()+. |
| * +NEVER+ means the configured value is evaluated once and never updated. All changes are silently discarded. |
| * +LOG_AND_DISCARD+ similar to +NEVER+, but changes are logged before they are discarded. |
| |
| Summarizing a +DynamicValue+ allows you |
| |
| * to reload actively updates of configured values. |
| * update implicitly or explicitly all changes on the value. |
| * add listeners that observe changes of a certain value. |
| |
| Dynamic values also allow on-the-fly reevaluation of the value by calling +evaluateValue()+. Hereby the value of the |
| instance is not changed. |
| |
| |
| |
| === Configuration Events |
| |
| Similar to CDI Tamaya publishes Configuration events, when instances were configured. It depends on the effective |
| event backend in use, if and how events are published: |
| |
| * when you have the CDI extension active events are published using the default CDI event mechanism. |
| * in all other scenarios events are delegated to the +tamaya-events+ module, if available, |
| * if no event delegation is available no events are published. |
| |
| The event published is very simple: |
| |
| [source,java] |
| -------------------------------------------- |
| public interface ConfiguredType { |
| Class getType(); |
| String getName(); |
| Collection<ConfiguredField> getConfiguredFields(); |
| Collection<ConfiguredMethod> getConfiguredMethods(); |
| void configure(Object instance, Configuration config); |
| } |
| |
| |
| public interface ConfiguredField { |
| Class<?> getType(); |
| Collection<String> getConfiguredKeys(); |
| String getName(); |
| String getSignature(); |
| Field getAnnotatedField(); |
| void configure(Object instance, Configuration config); |
| } |
| |
| public interface ConfiguredMethod { |
| Collection<String> getConfiguredKeys(); |
| Class<?>[] getParameterTypes(); |
| Method getAnnotatedMethod(); |
| String getName(); |
| String getSignature(); |
| void configure(Object instance, Configuration config); |
| } |
| ---------------------------------------- |