您可以通过创建使用 @ConfigurationProperties 注释的类来创建类型安全的配置。
Micronaut 会产生一个无反射的@ConfigurationProperties bean,并且还会在编译时计算属性路径进行评估,大大提高加载@ConfigurationProperties 的速度和效率。
例如:
@ConfigurationProperties Example
Java |
Groovy |
Kotlin |
import io.micronaut.context.annotation.ConfigurationProperties;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import java.util.Optional;
@ConfigurationProperties("my.engine") // (1)
public class EngineConfig {
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public int getCylinders() {
return cylinders;
}
public void setCylinders(int cylinders) {
this.cylinders = cylinders;
}
public CrankShaft getCrankShaft() {
return crankShaft;
}
public void setCrankShaft(CrankShaft crankShaft) {
this.crankShaft = crankShaft;
}
@NotBlank // (2)
private String manufacturer = "Ford"; // (3)
@Min(1L)
private int cylinders;
private CrankShaft crankShaft = new CrankShaft();
@ConfigurationProperties("crank-shaft")
public static class CrankShaft { // (4)
private Optional<Double> rodLength = Optional.empty(); // (5)
public Optional<Double> getRodLength() {
return rodLength;
}
public void setRodLength(Optional<Double> rodLength) {
this.rodLength = rodLength;
}
}
}
|
import io.micronaut.context.annotation.ConfigurationProperties
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
@ConfigurationProperties('my.engine') // (1)
class EngineConfig {
@NotBlank // (2)
String manufacturer = "Ford" // (3)
@Min(1L)
int cylinders
CrankShaft crankShaft = new CrankShaft()
@ConfigurationProperties('crank-shaft')
static class CrankShaft { // (4)
Optional<Double> rodLength = Optional.empty() // (5)
}
}
|
import io.micronaut.context.annotation.ConfigurationProperties
import java.util.Optional
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
@ConfigurationProperties("my.engine") // (1)
class EngineConfig {
@NotBlank // (2)
var manufacturer = "Ford" // (3)
@Min(1L)
var cylinders: Int = 0
var crankShaft = CrankShaft()
@ConfigurationProperties("crank-shaft")
class CrankShaft { // (4)
var rodLength: Optional<Double> = Optional.empty() // (5)
}
}
|
@ConfigurationProperties 注解采用配置前缀
您可以使用 javax.validation 注释来验证配置
可以为属性分配默认值
静态内部类可以提供嵌套配置
可选的配置值可以包装在 java.util.Optional 中
一旦你准备好了一个类型安全的配置,它就可以像任何其他 bean 一样注入到你的 bean 中:
@ConfigurationProperties 依赖注入
Java |
Groovy |
Kotlin |
@Singleton
public class EngineImpl implements Engine {
private final EngineConfig config;
public EngineImpl(EngineConfig config) { // (1)
this.config = config;
}
@Override
public int getCylinders() {
return config.getCylinders();
}
@Override
public String start() {// (2)
return getConfig().getManufacturer() + " Engine Starting V" + getConfig().getCylinders() +
" [rodLength=" + getConfig().getCrankShaft().getRodLength().orElse(6d) + "]";
}
public final EngineConfig getConfig() {
return config;
}
}
|
@Singleton
class EngineImpl implements Engine {
final EngineConfig config
EngineImpl(EngineConfig config) { // (1)
this.config = config
}
@Override
int getCylinders() {
config.cylinders
}
@Override
String start() { // (2)
"$config.manufacturer Engine Starting V$config.cylinders [rodLength=${config.crankShaft.rodLength.orElse(6.0d)}]"
}
}
|
@Singleton
class EngineImpl(val config: EngineConfig) : Engine {// (1)
override val cylinders: Int
get() = config.cylinders
override fun start(): String {// (2)
return "${config.manufacturer} Engine Starting V${config.cylinders} [rodLength=${config.crankShaft.rodLength.orElse(6.0)}]"
}
}
|
注入 EngineConfig bean
使用配置属性
然后可以从 PropertySource 实例之一提供配置值。例如:
供应配置
Java |
Groovy |
Kotlin |
Map<String, Object> map = new LinkedHashMap<>(1);
map.put("my.engine.cylinders", "8");
ApplicationContext applicationContext = ApplicationContext.run(map, "test");
Vehicle vehicle = applicationContext.getBean(Vehicle.class);
System.out.println(vehicle.start());
|
ApplicationContext applicationContext = ApplicationContext.run(
['my.engine.cylinders': '8'],
"test"
)
def vehicle = applicationContext.getBean(Vehicle)
println(vehicle.start())
|
val map = mapOf( "my.engine.cylinders" to "8")
val applicationContext = ApplicationContext.run(map, "test")
val vehicle = applicationContext.getBean(Vehicle::class.java)
println(vehicle.start())
|
上面的示例打印:“Ford Engine Starting V8 [rodLength=6.0]”
您可以使用以下语法直接引用@Requires 注释中的配置属性以有条件地加载bean:@Requires(bean=Config.class, beanProperty="property", value="true")
请注意,对于更复杂的配置,您可以通过继承构造 @ConfigurationProperties bean。
例如,使用 @ConfigurationProperties('bar') 创建 EngineConfig 的子类将解析路径 my.engine.bar 下的所有属性。
包括/排除
对于配置属性类从父类继承属性的情况,可能需要从父类中排除属性。 @ConfigurationProperties 注释的包含和排除成员允许该功能。该列表适用于本地属性和继承属性。
提供给包含/排除列表的名称必须是“属性”名称。例如,如果注入了 setter 方法,则属性名称是去大写的 setter 名称(setConnectionTimeout → connectionTimeout)。
更改访问器样式
从 3.3 开始,Micronaut 支持为 getter 和 setter 定义不同的访问器前缀,而不是为 Java Beans 定义的默认 get 和 set。使用@AccessorsStyle 注释注释您的POJO 或@ConfigurationProperties 类。
当您以流畅的方式编写 getter 和 setter 时,这很有用。例如:
使用@AccessorsStyle
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.AccessorsStyle;
@AccessorsStyle(readPrefixes = "", writePrefixes = "") (1)
@ConfigurationProperties("my.engine")
public class EngineConfig {
private String manufacturer;
private int cylinders;
public EngineConfig(String manufacturer, int cylinders) {
this.manufacturer = manufacturer;
this.cylinders = cylinders;
}
public String manufacturer() { (2)
return manufacturer;
}
public void manufacturer(String manufacturer) { (2)
this.manufacturer = manufacturer;
}
public int cylinders() { (2)
return cylinders;
}
public void cylinders(int cylinders) { (2)
this.cylinders = cylinders;
}
}
Micronaut 将为 getter 和 setter 使用空前缀。
使用空前缀定义 getter 和 setter。
现在您可以注入 EngineConfig 并将其与 engineConfig.manufacturer() 和 engineConfig.cylinders() 一起使用以从配置中检索值。
属性类型转换
Micronaut 在解析属性时使用 ConversionService bean 来转换值。您可以通过定义实现 TypeConverter 接口的 bean 来为 Micronaut 不支持的类型注册额外的转换器。
Micronaut 具有一些有用的内置转换,下面将详细介绍。
持续时间转换
可以通过在单位后面附加一个数字来指定持续时间。支持的单位有 s、ms、m 等。下表总结了示例:
表 1. 持续时间转换
配置值 |
结果值 |
10ms
|
持续时间为 10 毫秒
|
10m
|
持续时间为 10 分钟
|
10s
|
持续时间为 10 秒
|
10d
|
持续时间为 10 天
|
10h
|
持续时间为 10 小时
|
10ns
|
持续时间为 10 纳秒
|
PT15M
|
使用ISO-8601格式,持续时间为15分钟
|
例如配置默认的 HTTP 客户端读取超时:
使用持续时间值
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
micronaut.http.client.read-timeout=15s
|
micronaut:
http:
client:
read-timeout: 15s
|
[micronaut]
[micronaut.http]
[micronaut.http.client]
read-timeout="15s"
|
micronaut {
http {
client {
readTimeout = "15s"
}
}
}
|
{
micronaut {
http {
client {
read-timeout = "15s"
}
}
}
}
|
{
"micronaut": {
"http": {
"client": {
"read-timeout": "15s"
}
}
}
}
|
列表/数组转换
列表和数组可以在 Java 属性文件中指定为逗号分隔值,或者在 YAML 中使用本机 YAML 列表。通用类型用于转换值。例如在 YAML 中:
在 YAML 中指定列表或数组
Properties |
Yaml |
Toml |
Groovy |
Hocon |
JSON |
my.app.integers[0]=1
my.app.integers[1]=2
my.app.urls[0]=http://foo.com
my.app.urls[1]=http://bar.com
|
my:
app:
integers:
- 1
- 2
urls:
- http://foo.com
- http://bar.com
|
[my]
[my.app]
integers=[
1,
2
]
urls=[
"http://foo.com",
"http://bar.com"
]
|
my {
app {
integers = [1, 2]
urls = ["http://foo.com", "http://bar.com"]
}
}
|
{
my {
app {
integers = [1, 2]
urls = ["http://foo.com", "http://bar.com"]
}
}
}
|
{
"my": {
"app": {
"integers": [1, 2],
"urls": ["http://foo.com", "http://bar.com"]
}
}
}
|
对于上面的示例配置,您可以定义属性以绑定到通过泛型提供的目标类型:
List<Integer> integers;
List<URL> urls;
可读字节
您可以使用 @ReadableBytes 注释任何设置器参数,以允许使用用于指定字节、千字节等的简写语法设置值。例如,以下内容取自 HttpClientConfiguration:
使用@ReadableBytes
public void setMaxContentLength(@ReadableBytes int maxContentLength) {
this.maxContentLength = maxContentLength;
}
准备好上述内容后,您可以使用以下值设置 micronaut.http.client.max-content-length:
表 2. @ReadableBytes 转换
配置值 |
结果值 |
10mb
|
10 MB
|
10kb
|
10 KB
|
10gb
|
10 GB
|
1024
|
原始字节长度
|
格式化日期
@Format 注释可用于 setter 以指定绑定 java.time 日期对象时要使用的日期格式。
对日期使用@Format
public void setMyDate(@Format("yyyy-MM-dd") LocalDate date) {
this.myDate = date;
}
配置生成器
许多框架和工具已经使用构建器风格的类来构造配置。
您可以使用 @ConfigurationBuilder 注释来使用配置值填充构建器样式的类。 ConfigurationBuilder 可以应用于用@ConfigurationProperties 注释的类中的字段或方法。
由于在 Java 世界中没有一致的方法来定义构建器,因此可以在注释中指定一个或多个方法前缀以支持构建器方法,如 withXxx 或 setXxx。如果构建器方法没有前缀,则为参数分配一个空字符串。
还可以指定配置前缀来告诉 Micronaut 在哪里查找配置值。默认情况下,构建器方法使用在类级 @ConfigurationProperties 注释中指定的配置前缀。
例如:
@ConfigurationBuilder 示例
Java |
Groovy |
Kotlin |
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 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()
}
|
@ConfigurationProperties 注解采用配置前缀
第一个构建器可以在没有类配置前缀的情况下进行配置;它继承自上面的。
第二个构建器可以配置类配置前缀 + configurationPrefix 值。
第三个构建器演示了注释可以应用于方法和属性。
默认情况下,仅支持单参数构建器方法。对于没有参数的方法,将注释的 allowZeroArgs 参数设置为 true。
与前面的示例一样,我们可以构造一个 EngineImpl。由于我们使用的是构建器,因此我们可以使用工厂类从构建器构建引擎。
Factory Bean
Java |
Groovy |
Kotlin |
import io.micronaut.context.annotation.Factory;
import jakarta.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 jakarta.inject.Singleton
@Factory
class EngineFactory {
@Singleton
EngineImpl buildEngine(EngineConfig engineConfig) {
engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
}
}
|
import io.micronaut.context.annotation.Factory
import jakarta.inject.Singleton
@Factory
internal class EngineFactory {
@Singleton
fun buildEngine(engineConfig: EngineConfig): EngineImpl {
return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
}
}
|
然后可以将返回的发动机注入需要发动机的任何地方。
可以从 PropertySource 实例之一提供配置值。例如:
供应配置
Java |
Groovy |
Kotlin |
Map<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())
|
上面的示例打印:“Subaru Engine Starting V4 [rodLength=4.0, sparkPlug=Iridium(NGK 6619 LFR6AIX)]”
MapFormat
对于某些用例,可能需要接受可以提供给 bean 的任意配置属性的映射,尤其是当 bean 代表第三方 API 时,其中并非所有可能的配置属性都是已知的。例如,数据源可以接受特定于特定数据库驱动程序的配置属性映射,允许用户在映射中指定任何所需的选项,而无需显式编码每个属性。
为此,MapFormat 注释允许您将映射绑定到单个配置属性,并指定是接受键到值的平面映射,还是接受嵌套映射(其中值可能是附加映射)。
@MapFormat 示例
Java |
Groovy |
Kotlin |
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.convert.format.MapFormat;
import javax.validation.constraints.Min;
import java.util.Map;
@ConfigurationProperties("my.engine")
public class EngineConfig {
@Min(1L)
private int cylinders;
@MapFormat(transformation = MapFormat.MapTransformation.FLAT) //(1)
private Map<Integer, String> sensors;
public int getCylinders() {
return cylinders;
}
public void setCylinders(int cylinders) {
this.cylinders = cylinders;
}
public Map<Integer, String> getSensors() {
return sensors;
}
public void setSensors(Map<Integer, String> sensors) {
this.sensors = sensors;
}
}
|
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.convert.format.MapFormat
import javax.validation.constraints.Min
@ConfigurationProperties('my.engine')
class EngineConfig {
@Min(1L)
int cylinders
@MapFormat(transformation = MapFormat.MapTransformation.FLAT) //(1)
Map<Integer, String> sensors
}
|
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.convert.format.MapFormat
import javax.validation.constraints.Min
@ConfigurationProperties("my.engine")
class EngineConfig {
@Min(1L)
var cylinders: Int = 0
@MapFormat(transformation = MapFormat.MapTransformation.FLAT) //(1)
var sensors: Map<Int, String>? = null
}
|
注意注释的转换参数;可能的值是 MapTransformation.FLAT(对于平面地图)和 MapTransformation.NESTED(对于嵌套地图)
EngineImpl
Java |
Groovy |
Kotlin |
@Singleton
public class EngineImpl implements Engine {
@Inject
EngineConfig config;
@Override
public Map getSensors() {
return config.getSensors();
}
@Override
public String start() {
return "Engine Starting V" + getConfig().getCylinders() +
" [sensors=" + getSensors().size() + "]";
}
public EngineConfig getConfig() {
return config;
}
public void setConfig(EngineConfig config) {
this.config = config;
}
}
|
@Singleton
class EngineImpl implements Engine {
@Inject EngineConfig config
@Override
Map getSensors() {
config.sensors
}
@Override
String start() {
"Engine Starting V$config.cylinders [sensors=${sensors.size()}]"
}
}
|
@Singleton
class EngineImpl : Engine {
override val sensors: Map<*, *>?
get() = config!!.sensors
@Inject
var config: EngineConfig? = null
override fun start(): String {
return "Engine Starting V${config!!.cylinders} [sensors=${sensors!!.size}]"
}
}
|
现在可以将属性映射提供给 my.engine.sensors 配置属性。
使用地图配置
Java |
Groovy |
Kotlin |
Map<String, Object> map = new LinkedHashMap<>(2);
map.put("my.engine.cylinders", "8");
Map<Integer, String> map1 = new LinkedHashMap<>(2);
map1.put(0, "thermostat");
map1.put(1, "fuel pressure");
map.put("my.engine.sensors", map1);
ApplicationContext applicationContext = ApplicationContext.run(map, "test");
Vehicle vehicle = applicationContext.getBean(Vehicle.class);
System.out.println(vehicle.start());
|
ApplicationContext applicationContext = ApplicationContext.run(
['my.engine.cylinders': '8',
'my.engine.sensors' : [0: 'thermostat',
1: 'fuel pressure']],
"test"
)
def vehicle = applicationContext.getBean(Vehicle)
println(vehicle.start())
|
val subMap = mapOf(
0 to "thermostat",
1 to "fuel pressure"
)
val map = mapOf(
"my.engine.cylinders" to "8",
"my.engine.sensors" to subMap
)
val applicationContext = ApplicationContext.run(map, "test")
val vehicle = applicationContext.getBean(Vehicle::class.java)
println(vehicle.start())
|
上面的示例打印:“Engine Starting V8 [sensors=2]”
更多建议: