Mapping configuration to objects
通过配置映射,可以将多个配置属性分组到一个共享相同前缀的单个接口中。
@ConfigMapping
配置映射需要一个带有最小元数据配置的公共接口,并使用 @io.smallrye.config.ConfigMapping
注解进行注释。
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
}
Server
接口能够使用名称 server.host
将配置属性映射到 Server.host()
方法并映射到 server.port
方法。要查找的配置属性名称是由前缀和方法名称(以 .
(点)作为分隔符)构建的。
如果映射未匹配到配置属性,则会抛出 |
Registration
当 Quarkus 应用程序启动时,可以将配置映射注册两次。一次用于 STATIC INIT,第二次用于 RUNTIME INIT:
STATIC INIT
Quarkus 在静态初始化期间启动其一些服务,而 Config
通常是最先创建的事物之一。在特定情况下可能无法正确初始化配置映射。例如,如果映射需要来自自定义 ConfigSource
的值。出于此原因,任何配置映射都需要 @io.quarkus.runtime.configuration.StaticInitSafe
注解将映射标记为在此阶段安全使用。了解有关自定义 ConfigSource
的 registration 的更多信息。
Retrieval
可以将配置映射接口注入到任何 CDI 感知 Bean 中:
class BusinessBean {
@Inject
Server server;
public void businessMethod() {
String host = server.host();
}
}
在非 CDI 上下文中,使用 API io.smallrye.config.SmallRyeConfig#getConfigMapping
检索配置映射实例:
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
Server server = config.getConfigMapping(Server.class);
Nested groups
嵌套映射提供了一种对其他配置属性进行子分组的方法:
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
Log log();
interface Log {
boolean enabled();
String suffix();
boolean rotate();
}
}
server.host=localhost
server.port=8080
server.log.enabled=true
server.log.suffix=.log
server.log.rotate=false
映射组的方法名称充当配置属性的子名称空间。
Overriding property names
@WithName
如果方法名称或属性名称彼此不匹配,`@WithName`注释可以覆盖方法名称映射并使用注释中提供的名称:
@ConfigMapping(prefix = "server")
public interface Server {
@WithName("name")
String host();
int port();
}
server.name=localhost
server.port=8080
@WithParentName
`@WithParentName`注释允许配置映射继承其容器名称,简化了与映射匹配所需的配置属性名称:
interface Server {
@WithParentName
ServerHostAndPort hostAndPort();
@WithParentName
ServerInfo info();
}
interface ServerHostAndPort {
String host();
int port();
}
interface ServerInfo {
String name();
}
server.host=localhost
server.port=8080
server.name=konoha
如果没有 @WithParentName
,方法 name()`需要配置属性 `server.info.name
。因为我们使用了 @WithParentName
,所以 info()`映射将从 `Server`中继承父名称,`name()`反而映射到了 `server.name
。
NamingStrategy
使用骆驼式命名法的方法名称映射为短横线分隔式属性名称:
@ConfigMapping(prefix = "server")
public interface Server {
String theHost();
int thePort();
}
server.the-host=localhost
server.the-port=8080
可以通过在 `@ConfigMapping`注释中设置 `namingStrategy`值来调整映射策略:
@ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM)
public interface ServerVerbatimNamingStrategy {
String theHost();
int thePort();
}
server.theHost=localhost
server.thePort=8080
`@ConfigMapping`注释支持以下命名策略:
-
KEBAB_CASE
(默认)- 方法名称通过使用破折号替换大小写变化而导出,以映射配置属性。 -
VERBATIM
- 使用方法名称原样映射配置属性。 -
SNAKE_CASE
- 方法名称通过使用下划线替换大小写变化而导出,以映射配置属性。
Conversions
配置映射类支持自动转换 `Config`中可用转换的所有类型:
@ConfigMapping
public interface SomeTypes {
@WithName("int")
int intPrimitive();
@WithName("int")
Integer intWrapper();
@WithName("long")
long longPrimitive();
@WithName("long")
Long longWrapper();
@WithName("float")
float floatPrimitive();
@WithName("float")
Float floatWrapper();
@WithName("double")
double doublePrimitive();
@WithName("double")
Double doubleWrapper();
@WithName("char")
char charPrimitive();
@WithName("char")
Character charWrapper();
@WithName("boolean")
boolean booleanPrimitive();
@WithName("boolean")
Boolean booleanWrapper();
}
int=9
long=9999999999
float=99.9
double=99.99
char=c
boolean=true
这也对 `Optional`及其支持的类型有效:
@ConfigMapping
public interface Optionals {
Optional<Server> server();
Optional<String> optional();
@WithName("optional.int")
OptionalInt optionalInt();
interface Server {
String host();
int port();
}
}
在这种情况下,如果没有配置属性与映射匹配,映射不会失败。
@WithConverter
`@WithConverter`注释提供了一种在特定映射中使用 `Converter`的方法:
@ConfigMapping
public interface Converters {
@WithConverter(FooBarConverter.class)
String foo();
}
public static class FooBarConverter implements Converter<String> {
@Override
public String convert(final String value) {
return "bar";
}
}
foo=foo
对 Converters.foo()`的调用会生成值 `bar
。
Collections
配置映射还可以映射集合类型 List`和 `Set
:
@ConfigMapping(prefix = "server")
public interface ServerCollections {
Set<Environment> environments();
interface Environment {
String name();
List<App> apps();
interface App {
String name();
List<String> services();
Optional<List<String>> databases();
}
}
}
server.environments[0].name=dev
server.environments[0].apps[0].name=rest
server.environments[0].apps[0].services=bookstore,registration
server.environments[0].apps[0].databases=pg,h2
server.environments[0].apps[1].name=batch
server.environments[0].apps[1].services=stock,warehouse
List`或 `Set`映射可以使用 indexed properties在映射组中映射配置值。对于具有简单元素类型的集合(如 `String
),其配置值是一个逗号分隔的字符串。
Maps
配置映射还可以映射 Map
:
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
Map<String, String> form();
}
server.host=localhost
server.port=8080
server.form.login-page=login.html
server.form.error-page=error.html
server.form.landing-page=index.html
配置属性需要指定要作为键的附加名称。在这种情况下,form()
Map`将包含带有键 `login-page
、`error-page`和 `landing-page`的三元素。
它也适用于组:
@ConfigMapping(prefix = "server")
public interface Servers {
@WithParentName
Map<String, Server> allServers();
}
public interface Server {
String host();
int port();
String login();
String error();
String landing();
}
server."my-server".host=localhost
server."my-server".port=8080
server."my-server".login=login.html
server."my-server".error=error.html
server."my-server".landing=index.html
在这种情况下,allServers()
`Map`将包含一个带有键 `my-server`的 `Server`元素。
Defaults
`@WithDefault`注释允许将默认属性设置为映射(如果配置值在任何 `ConfigSource`中不可用,则阻止和错误):
public interface Defaults {
@WithDefault("foo")
String foo();
@WithDefault("bar")
String bar();
}
不需要配置属性。Defaults.foo()`将返回值 `foo
,而 Defaults.bar()`将返回值 `bar
。
Validation
配置映射可以组合来自 Bean Validation的注释来验证配置值:
@ConfigMapping(prefix = "server")
public interface Server {
@Size(min = 2, max = 20)
String host();
@Max(10000)
int port();
}
为了使验证工作,`quarkus-hibernate-validator`扩展是必需的,并且会自动执行。
Mocking
映射接口实现不是代理,因此不能用 `@InjectMock`像其他 CDI bean一样直接模拟它。一种技巧是用生产者方法使它可代理:
public class ServerMockProducer {
@Inject
Config config;
@Produces
@ApplicationScoped
@io.quarkus.test.Mock
Server server() {
return config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class);
}
}
`Server`可以用 `@InjectMock`注入到 Quarkus 测试类中作为模拟:
@QuarkusTest
class ServerMockTest {
@InjectMock
Server server;
@Test
void localhost() {
Mockito.when(server.host()).thenReturn("localhost");
assertEquals("localhost", server.host());
}
}
模拟只是一个没有实际配置值的空壳。 |
如果目标只是模拟某些配置值并保留原始配置,则模拟实例需要一个间谍:
@ConfigMapping(prefix = "app")
public interface AppConfig {
@WithDefault("app")
String name();
Info info();
interface Info {
@WithDefault("alias")
String alias();
@WithDefault("10")
Integer count();
}
}
public static class AppConfigProducer {
@Inject
Config config;
@Produces
@ApplicationScoped
@io.quarkus.test.Mock
AppConfig appConfig() {
AppConfig appConfig = config.unwrap(SmallRyeConfig.class).getConfigMapping(AppConfig.class);
AppConfig appConfigSpy = Mockito.spy(appConfig);
AppConfig.Info infoSpy = Mockito.spy(appConfig.info());
Mockito.when(appConfigSpy.info()).thenReturn(infoSpy);
return appConfigSpy;
}
}
`AppConfig`可以用 `@Inject`注入到 Quarkus 测试类中作为模拟:
@QuarkusTest
class AppConfigTest {
@Inject
AppConfig appConfig;
@Test
void localhost() {
Mockito.when(appConfig.name()).thenReturn("mocked-app");
assertEquals("mocked-app", server.host());
Mockito.when(appConfig.info().alias()).thenReturn("mocked-alias");
assertEquals("mocked-alias", server.info().alias());
}
}
嵌套元素需要由 Mockito 单独监视。 |