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 their own distinct configuration. That is where EachProperty comes in.

The @EachProperty annotation will create a ConfigurationProperties bean for each sub-property within the given property. As an example consider the following class:

Using @EachProperty

  1. import io.micronaut.context.annotation.Parameter;
  2. import io.micronaut.context.annotation.EachProperty;
  3. @EachProperty("test.datasource") (1)
  4. public class DataSourceConfiguration {
  5. private final String name;
  6. private URI url = new URI("localhost");
  7. public DataSourceConfiguration(@Parameter String name) (2)
  8. throws URISyntaxException {
  9. this.name = name;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public URI getUrl() { (3)
  15. return url;
  16. }
  17. public void setUrl(URI url) {
  18. this.url = url;
  19. }
  20. }

Using @EachProperty

  1. @EachProperty("test.datasource")
  2. (1)
  3. class DataSourceConfiguration {
  4. final String name
  5. URI url = new URI("localhost")
  6. DataSourceConfiguration(@Parameter String name) (2)
  7. throws URISyntaxException {
  8. this.name = name
  9. }
  10. }

Using @EachProperty

  1. @EachProperty("test.datasource") (1)
  2. class DataSourceConfiguration (2)
  3. @Throws(URISyntaxException::class)
  4. constructor(@param:Parameter val name: String) {
  5. (3)
  6. var url = URI("localhost")
  7. }
1The @EachProperty annotation defines the property name that should be handled.
2The @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)
3Each property of the bean is bound to configuration.

The above DataSourceConfiguration defines a url property to configure one or many hypothetical data sources of some sort. The URLs themselves can be configured using any of the PropertySource instances evaluated to Micronaut:

Providing Configuration to @EachProperty

  1. ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
  2. "test",
  3. CollectionUtils.mapOf(
  4. "test.datasource.one.url", "jdbc:mysql://localhost/one",
  5. "test.datasource.two.url", "jdbc:mysql://localhost/two")
  6. ));

Providing Configuration to @EachProperty

  1. ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
  2. "test",
  3. [
  4. "test.datasource.one.url": "jdbc:mysql://localhost/one",
  5. "test.datasource.two.url": "jdbc:mysql://localhost/two"
  6. ]
  7. ))

Providing Configuration to @EachProperty

  1. val applicationContext = ApplicationContext.run(PropertySource.of(
  2. "test",
  3. mapOf(
  4. "test.datasource.one.url" to "jdbc:mysql://localhost/one",
  5. "test.datasource.two.url" to "jdbc:mysql://localhost/two"
  6. )
  7. ))

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

  1. Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class);
  2. assertEquals(2, beansOfType.size()); (1)
  3. DataSourceConfiguration firstConfig = applicationContext.getBean(
  4. DataSourceConfiguration.class,
  5. Qualifiers.byName("one") (2)
  6. );
  7. assertEquals(
  8. new URI("jdbc:mysql://localhost/one"),
  9. firstConfig.getUrl()
  10. );

Evaluating Beans Built by @EachProperty

  1. when:
  2. Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class)
  3. assertEquals(2, beansOfType.size()) (1)
  4. DataSourceConfiguration firstConfig = applicationContext.getBean(
  5. DataSourceConfiguration.class,
  6. Qualifiers.byName("one") (2)
  7. )
  8. then:
  9. new URI("jdbc:mysql://localhost/one") == firstConfig.getUrl()

Evaluating Beans Built by @EachProperty

  1. val beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration::class.java)
  2. assertEquals(2, beansOfType.size.toLong()) (1)
  3. val firstConfig = applicationContext.getBean(
  4. DataSourceConfiguration::class.java,
  5. Qualifiers.byName("one") (2)
  6. )
  7. assertEquals(
  8. URI("jdbc:mysql://localhost/one"),
  9. firstConfig.url
  10. )
1All beans of type DataSourceConfiguration can be retrieved using getBeansOfType
2Individual beans can be achieved 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 the class should be bound from a list. Simply set the list member on the annotation to true.

@EachProperty List Example

  1. /*
  2. * Copyright 2017-2020 original authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package io.micronaut.docs.config.env;
  17. import io.micronaut.context.annotation.EachProperty;
  18. import io.micronaut.context.annotation.Parameter;
  19. import io.micronaut.core.order.Ordered;
  20. import java.time.Duration;
  21. @EachProperty(value = "ratelimits", list = true) (1)
  22. public class RateLimitsConfiguration implements Ordered { (2)
  23. private final Integer index;
  24. private Duration period;
  25. private Integer limit;
  26. RateLimitsConfiguration(@Parameter Integer index) { (3)
  27. this.index = index;
  28. }
  29. @Override
  30. public int getOrder() {
  31. return index;
  32. }
  33. public Duration getPeriod() {
  34. return period;
  35. }
  36. public void setPeriod(Duration period) {
  37. this.period = period;
  38. }
  39. public Integer getLimit() {
  40. return limit;
  41. }
  42. public void setLimit(Integer limit) {
  43. this.limit = limit;
  44. }
  45. }

@EachProperty List Example

  1. /*
  2. * Copyright 2017-2020 original authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package io.micronaut.docs.config.env
  17. import io.micronaut.context.annotation.EachProperty
  18. import io.micronaut.context.annotation.Parameter
  19. import io.micronaut.core.order.Ordered
  20. import java.time.Duration
  21. @EachProperty(value = "ratelimits", list = true) (1)
  22. class RateLimitsConfiguration implements Ordered { (2)
  23. private final Integer index
  24. Duration period
  25. Integer limit
  26. RateLimitsConfiguration(@Parameter Integer index) { (3)
  27. this.index = index
  28. }
  29. @Override
  30. int getOrder() {
  31. index
  32. }
  33. }

@EachProperty List Example

  1. /*
  2. * Copyright 2017-2020 original authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package io.micronaut.docs.config.env
  17. import io.micronaut.context.annotation.EachProperty
  18. import io.micronaut.context.annotation.Parameter
  19. import io.micronaut.core.order.Ordered
  20. import java.time.Duration
  21. @EachProperty(value = "ratelimits", list = true) (1)
  22. class RateLimitsConfiguration
  23. constructor(@param:Parameter private val index: Int) (3)
  24. : Ordered { (2)
  25. var period: Duration? = null
  26. var limit: Int? = null
  27. override fun getOrder(): Int {
  28. return index
  29. }
  30. }
1The list member of the annotation is set to true
2Implement Ordered if order matters when retrieving the beans
3The index is injected into the constructor