Micronaut 配置属性

2023-03-01 16:03 更新

您可以通过创建使用 @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)
    }
}
  1. @ConfigurationProperties 注解采用配置前缀

  2. 您可以使用 javax.validation 注释来验证配置

  3. 可以为属性分配默认值

  4. 静态内部类可以提供嵌套配置

  5. 可选的配置值可以包装在 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)}]"
    }
}
  1. 注入 EngineConfig bean

  2. 使用配置属性

然后可以从 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;
    }

}
  1. Micronaut 将为 getter 和 setter 使用空前缀。

  2. 使用空前缀定义 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()
}
  1. @ConfigurationProperties 注解采用配置前缀

  2. 第一个构建器可以在没有类配置前缀的情况下进行配置;它继承自上面的。

  3. 第二个构建器可以配置类配置前缀 + configurationPrefix 值。

  4. 第三个构建器演示了注释可以应用于方法和属性。

默认情况下,仅支持单参数构建器方法。对于没有参数的方法,将注释的 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
}
  1. 注意注释的转换参数;可能的值是 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]”


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号