Configuration Builder

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

You can use the @ConfigurationBuilder annotation to populate a builder-style class with configuration values. ConfigurationBuilder can be applied to fields or methods 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, builder methods use the configuration prefix specified in a 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() {
  11. return sparkPlug;
  12. }
  13. @ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") (4)
  14. void setSparkPlug(SparkPlug.Builder sparkPlug) {
  15. this.sparkPlug = sparkPlug;
  16. }
  17. }
  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 inherits 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 single-argument builder methods are supported. For methods with no arguments, set the allowZeroArgs parameter of the annotation to true.

Like in the previous example, we can construct an EngineImpl. Since we are using a builder, we can use a factory class to build the engine from the builder.

  1. import io.micronaut.context.annotation.Factory;
  2. import jakarta.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 jakarta.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 jakarta.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 required.

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

  1. Map<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.getBean(Vehicle)
  12. 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)]"