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:
import io.micronaut.context.annotation.ConfigurationBuilder;
import io.micronaut.context.annotation.ConfigurationProperties;
@ConfigurationProperties("my.engine") (1)
class EngineConfig {
@ConfigurationBuilder(prefixes = "with") (2)
EngineImpl.Builder builder = EngineImpl.builder();
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") (3)
CrankShaft.Builder crankShaft = CrankShaft.builder();
private SparkPlug.Builder sparkPlug = SparkPlug.builder();
SparkPlug.Builder getSparkPlug() { return this.sparkPlug; }
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") (4)
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug;
}
}
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
@ConfigurationProperties('my.engine') (1)
class EngineConfig {
@ConfigurationBuilder(prefixes = "with") (2)
EngineImpl.Builder builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") (3)
CrankShaft.Builder crankShaft = CrankShaft.builder()
SparkPlug.Builder sparkPlug = SparkPlug.builder()
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") (4)
void setSparkPlug(SparkPlug.Builder sparkPlug) {
this.sparkPlug = sparkPlug
}
}
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
@ConfigurationProperties("my.engine") (1)
internal class EngineConfig {
@ConfigurationBuilder(prefixes = ["with"]) (2)
val builder = EngineImpl.builder()
@ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") (3)
val crankShaft = CrankShaft.builder()
@set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug") (4)
var sparkPlug = SparkPlug.builder()
}
1 | The @ConfigurationProperties annotation takes the configuration prefix |
2 | The first builder can be configured without the class configuration prefix; it will inherit from the above. |
3 | The second builder can be configured with the class configuration prefix + the configurationPrefix value. |
4 | The 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.
import io.micronaut.context.annotation.Factory;
import javax.inject.Singleton;
@Factory
class EngineFactory {
@Singleton
EngineImpl buildEngine(EngineConfig engineConfig) {
return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.getSparkPlug());
}
}
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class EngineFactory {
@Singleton
EngineImpl buildEngine(EngineConfig engineConfig) {
engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
}
}
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
internal class EngineFactory {
@Singleton
fun buildEngine(engineConfig: EngineConfig): EngineImpl {
return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
}
}
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:
HashMap<String, Object> properties = new HashMap<>();
properties.put("my.engine.cylinders" ,"4");
properties.put("my.engine.manufacturer" , "Subaru");
properties.put("my.engine.crank-shaft.rod-length", 4);
properties.put("my.engine.spark-plug.name" , "6619 LFR6AIX");
properties.put("my.engine.spark-plug.type" , "Iridium");
properties.put("my.engine.spark-plug.companyName", "NGK");
ApplicationContext applicationContext = ApplicationContext.run(properties, "test");
Vehicle vehicle = applicationContext.getBean(Vehicle.class);
System.out.println(vehicle.start());
ApplicationContext applicationContext = ApplicationContext.run(
['my.engine.cylinders' : '4',
'my.engine.manufacturer' : 'Subaru',
'my.engine.crank-shaft.rod-length': 4,
'my.engine.spark-plug.name' : '6619 LFR6AIX',
'my.engine.spark-plug.type' : 'Iridium',
'my.engine.spark-plug.companyName': 'NGK'
],
"test"
)
Vehicle vehicle = applicationContext
.getBean(Vehicle)
println(vehicle.start())
val applicationContext = ApplicationContext.run(
mapOf(
"my.engine.cylinders" to "4",
"my.engine.manufacturer" to "Subaru",
"my.engine.crank-shaft.rod-length" to 4,
"my.engine.spark-plug.name" to "6619 LFR6AIX",
"my.engine.spark-plug.type" to "Iridium",
"my.engine.spark-plug.company" to "NGK"
),
"test"
)
val vehicle = applicationContext.getBean(Vehicle::class.java)
println(vehicle.start())
The above example prints: "Subaru Engine Starting V4 [rodLength=4.0, sparkPlug=Iridium(NGK 6619 LFR6AIX)]"