Configuration Builder

Many existing frameworks and tools already use builder-style classes to construct configuration.

To support the ability for a builder style class to be populated with configuration values, the @ConfigurationBuilder annotation can be used. ConfigurationBuilder can be added to a field or method in a class annotated with @ConfigurationProperties.

Since there is no consistent way to define builders in the Java world, one or more method prefixes can be specified in the annotation to support builder methods like withXxx or setXxx. If the builder methods have no prefix, assign an empty string to the parameter.

A configuration prefix can also be specified to tell Micronaut where to look for configuration values. By default, the builder methods will use the configuration prefix defined at the class level @ConfigurationProperties annotation.

For example:

  1. import io.micronaut.context.annotation.ConfigurationBuilder;
  2. import io.micronaut.context.annotation.ConfigurationProperties;
  3. @ConfigurationProperties("my.engine") (1)
  4. class EngineConfig {
  5. @ConfigurationBuilder(prefixes = "with") (2)
  6. EngineImpl.Builder builder = EngineImpl.builder();
  7. @ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") (3)
  8. CrankShaft.Builder crankShaft = CrankShaft.builder();
  9. private SparkPlug.Builder sparkPlug = SparkPlug.builder();
  10. SparkPlug.Builder getSparkPlug() { return this.sparkPlug; }
  11. @ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") (4)
  12. void setSparkPlug(SparkPlug.Builder sparkPlug) {
  13. this.sparkPlug = sparkPlug;
  14. }
  15. }
  1. import io.micronaut.context.annotation.ConfigurationBuilder
  2. import io.micronaut.context.annotation.ConfigurationProperties
  3. @ConfigurationProperties('my.engine') (1)
  4. class EngineConfig {
  5. @ConfigurationBuilder(prefixes = "with") (2)
  6. EngineImpl.Builder builder = EngineImpl.builder()
  7. @ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") (3)
  8. CrankShaft.Builder crankShaft = CrankShaft.builder()
  9. SparkPlug.Builder sparkPlug = SparkPlug.builder()
  10. @ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") (4)
  11. void setSparkPlug(SparkPlug.Builder sparkPlug) {
  12. this.sparkPlug = sparkPlug
  13. }
  14. }
  1. import io.micronaut.context.annotation.ConfigurationBuilder
  2. import io.micronaut.context.annotation.ConfigurationProperties
  3. @ConfigurationProperties("my.engine") (1)
  4. internal class EngineConfig {
  5. @ConfigurationBuilder(prefixes = ["with"]) (2)
  6. val builder = EngineImpl.builder()
  7. @ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") (3)
  8. val crankShaft = CrankShaft.builder()
  9. @set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug") (4)
  10. var sparkPlug = SparkPlug.builder()
  11. }
1The @ConfigurationProperties annotation takes the configuration prefix
2The first builder can be configured without the class configuration prefix; it will inherit from the above.
3The second builder can be configured with the class configuration prefix + the configurationPrefix value.
4The third builder demonstrates that the annotation can be applied to a method as well as a property.
By default, only builder methods that take a single argument are supported. To support methods with no arguments, set the allowZeroArgs parameter of the annotation to true.

Just like in the previous example, we can construct an EngineImpl. Since we are using a builder, a factory class can be used to build the engine from the builder.

  1. import io.micronaut.context.annotation.Factory;
  2. import javax.inject.Singleton;
  3. @Factory
  4. class EngineFactory {
  5. @Singleton
  6. EngineImpl buildEngine(EngineConfig engineConfig) {
  7. return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.getSparkPlug());
  8. }
  9. }
  1. import io.micronaut.context.annotation.Factory
  2. import javax.inject.Singleton
  3. @Factory
  4. class EngineFactory {
  5. @Singleton
  6. EngineImpl buildEngine(EngineConfig engineConfig) {
  7. engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
  8. }
  9. }
  1. import io.micronaut.context.annotation.Factory
  2. import javax.inject.Singleton
  3. @Factory
  4. internal class EngineFactory {
  5. @Singleton
  6. fun buildEngine(engineConfig: EngineConfig): EngineImpl {
  7. return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
  8. }
  9. }

The engine that was returned can then be injected anywhere an engine is depended on.

Configuration values can be supplied from one of the PropertySource instances. For example:

  1. HashMap<String, Object> properties = new HashMap<>();
  2. properties.put("my.engine.cylinders" ,"4");
  3. properties.put("my.engine.manufacturer" , "Subaru");
  4. properties.put("my.engine.crank-shaft.rod-length", 4);
  5. properties.put("my.engine.spark-plug.name" , "6619 LFR6AIX");
  6. properties.put("my.engine.spark-plug.type" , "Iridium");
  7. properties.put("my.engine.spark-plug.companyName", "NGK");
  8. ApplicationContext applicationContext = ApplicationContext.run(properties, "test");
  9. Vehicle vehicle = applicationContext.getBean(Vehicle.class);
  10. System.out.println(vehicle.start());
  1. ApplicationContext applicationContext = ApplicationContext.run(
  2. ['my.engine.cylinders' : '4',
  3. 'my.engine.manufacturer' : 'Subaru',
  4. 'my.engine.crank-shaft.rod-length': 4,
  5. 'my.engine.spark-plug.name' : '6619 LFR6AIX',
  6. 'my.engine.spark-plug.type' : 'Iridium',
  7. 'my.engine.spark-plug.companyName': 'NGK'
  8. ],
  9. "test"
  10. )
  11. Vehicle vehicle = applicationContext
  12. .getBean(Vehicle)
  13. println(vehicle.start())
  1. val applicationContext = ApplicationContext.run(
  2. mapOf(
  3. "my.engine.cylinders" to "4",
  4. "my.engine.manufacturer" to "Subaru",
  5. "my.engine.crank-shaft.rod-length" to 4,
  6. "my.engine.spark-plug.name" to "6619 LFR6AIX",
  7. "my.engine.spark-plug.type" to "Iridium",
  8. "my.engine.spark-plug.company" to "NGK"
  9. ),
  10. "test"
  11. )
  12. val vehicle = applicationContext.getBean(Vehicle::class.java)
  13. println(vehicle.start())

The above example prints: "Subaru Engine Starting V4 [rodLength=4.0, sparkPlug=Iridium(NGK 6619 LFR6AIX)]"