4.6 Using @EachProperty to Drive Configuration
The @ConfigurationProperties annotation is great for a single configuration class, but sometimes you want multiple instances, each with its own distinct configuration. That is where EachProperty comes in.
The @EachProperty annotation creates a ConfigurationProperties
bean for each sub-property within the given property. As an example consider the following class:
Using @EachProperty
import java.net.URI;
import java.net.URISyntaxException;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.context.annotation.EachProperty;
@EachProperty("test.datasource") (1)
public class DataSourceConfiguration {
private final String name;
private URI url = new URI("localhost");
public DataSourceConfiguration(@Parameter String name) (2)
throws URISyntaxException {
this.name = name;
}
public String getName() {
return name;
}
public URI getUrl() { (3)
return url;
}
public void setUrl(URI url) {
this.url = url;
}
}
Using @EachProperty
import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
@EachProperty("test.datasource") (1)
class DataSourceConfiguration {
final String name
URI url = new URI("localhost") (3)
DataSourceConfiguration(@Parameter String name) (2)
throws URISyntaxException {
this.name = name
}
}
Using @EachProperty
import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import java.net.URI
import java.net.URISyntaxException
@EachProperty("test.datasource") (1)
class DataSourceConfiguration
@Throws(URISyntaxException::class)
constructor(@param:Parameter val name: String) { (2)
var url = URI("localhost") (3)
}
1 | The @EachProperty annotation defines the property name to be handled. |
2 | The @Parameter annotation can be used to inject the name of the sub-property that defines the name of the bean (which is also the bean qualifier) |
3 | Each property of the bean is bound to configuration. |
The above DataSourceConfiguration
defines a url
property to configure one or more data sources. The URLs themselves can be configured using any of the PropertySource instances evaluated to Micronaut:
Providing Configuration to @EachProperty
ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
"test",
CollectionUtils.mapOf(
"test.datasource.one.url", "jdbc:mysql://localhost/one",
"test.datasource.two.url", "jdbc:mysql://localhost/two")
));
Providing Configuration to @EachProperty
ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
"test",
[
"test.datasource.one.url": "jdbc:mysql://localhost/one",
"test.datasource.two.url": "jdbc:mysql://localhost/two"
]
))
Providing Configuration to @EachProperty
val applicationContext = ApplicationContext.run(PropertySource.of(
"test",
mapOf(
"test.datasource.one.url" to "jdbc:mysql://localhost/one",
"test.datasource.two.url" to "jdbc:mysql://localhost/two"
)
))
In the above example two data sources (called one
and two
) are defined under the test.datasource
prefix defined earlier in the @EachProperty
annotation. Each of these configuration entries triggers the creation of a new DataSourceConfiguration
bean such that the following test succeeds:
Evaluating Beans Built by @EachProperty
Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class);
assertEquals(2, beansOfType.size()); (1)
DataSourceConfiguration firstConfig = applicationContext.getBean(
DataSourceConfiguration.class,
Qualifiers.byName("one") (2)
);
assertEquals(
new URI("jdbc:mysql://localhost/one"),
firstConfig.getUrl()
);
Evaluating Beans Built by @EachProperty
when:
Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class)
assertEquals(2, beansOfType.size()) (1)
DataSourceConfiguration firstConfig = applicationContext.getBean(
DataSourceConfiguration.class,
Qualifiers.byName("one") (2)
)
then:
new URI("jdbc:mysql://localhost/one") == firstConfig.getUrl()
Evaluating Beans Built by @EachProperty
val beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration::class.java)
assertEquals(2, beansOfType.size) (1)
val firstConfig = applicationContext.getBean(
DataSourceConfiguration::class.java,
Qualifiers.byName("one") (2)
)
assertEquals(
URI("jdbc:mysql://localhost/one"),
firstConfig.url
)
1 | All beans of type DataSourceConfiguration can be retrieved using getBeansOfType |
2 | Individual beans can be retrieved by using the byName qualifier. |
List-Based Binding
The default behavior of @EachProperty is to bind from a map style of configuration, where the key is the named qualifier of the bean and the value is the data to bind from. For cases where map style configuration doesn’t make sense, it is possible to inform Micronaut that the class is bound from a list. Simply set the list
member on the annotation to true.
@EachProperty List Example
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.order.Ordered;
import java.time.Duration;
@EachProperty(value = "ratelimits", list = true) (1)
public class RateLimitsConfiguration implements Ordered { (2)
private final Integer index;
private Duration period;
private Integer limit;
RateLimitsConfiguration(@Parameter Integer index) { (3)
this.index = index;
}
@Override
public int getOrder() {
return index;
}
public Duration getPeriod() {
return period;
}
public void setPeriod(Duration period) {
this.period = period;
}
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
}
@EachProperty List Example
import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import io.micronaut.core.order.Ordered
import java.time.Duration
@EachProperty(value = "ratelimits", list = true) (1)
class RateLimitsConfiguration implements Ordered { (2)
private final Integer index
Duration period
Integer limit
RateLimitsConfiguration(@Parameter Integer index) { (3)
this.index = index
}
@Override
int getOrder() {
index
}
}
@EachProperty List Example
import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import io.micronaut.core.order.Ordered
import java.time.Duration
@EachProperty(value = "ratelimits", list = true) (1)
class RateLimitsConfiguration
constructor(@param:Parameter private val index: Int) (3)
: Ordered { (2)
var period: Duration? = null
var limit: Int? = null
override fun getOrder(): Int {
return index
}
}
1 | The list member of the annotation is set to true |
2 | Implement Ordered if order matters when retrieving the beans |
3 | The index is injected into the constructor |