Mapping configuration to objects
通过配置映射,可以将多个配置属性分组到一个共享相同前缀的单个接口中。
With config mappings it is possible to group multiple configuration properties in a single interface that share the same prefix.
@ConfigMapping
配置映射需要一个带有最小元数据配置的公共接口,并使用 @io.smallrye.config.ConfigMapping
注解进行注释。
A config mapping requires a public interface with minimal metadata configuration and annotated with the
@io.smallrye.config.ConfigMapping
annotation.
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
}
Server
接口能够使用名称 server.host
将配置属性映射到 Server.host()
方法并映射到 server.port
方法。要查找的配置属性名称是由前缀和方法名称(以 .
(点)作为分隔符)构建的。
The Server
interface is able to map configuration properties with the name server.host
into the Server.host()
method and server.port
into Server.port()
method. The configuration property name to look up is built from the
prefix, and the method name with .
(dot) as the separator.
如果映射未匹配到配置属性,则会抛出 |
If a mapping fails to match a configuration property a |
Registration
当 Quarkus 应用程序启动时,可以将配置映射注册两次。一次用于 STATIC INIT,第二次用于 RUNTIME INIT:
When a Quarkus application starts, a config mapping can be registered twice. One time for STATIC INIT and a second time for RUNTIME INIT:
STATIC INIT
Quarkus 在静态初始化期间启动其一些服务,而 Config
通常是最先创建的事物之一。在特定情况下可能无法正确初始化配置映射。例如,如果映射需要来自自定义 ConfigSource
的值。出于此原因,任何配置映射都需要 @io.quarkus.runtime.configuration.StaticInitSafe
注解将映射标记为在此阶段安全使用。了解有关自定义 ConfigSource
的 registration 的更多信息。
Quarkus starts some of its services during static initialization, and Config
is usually one of the first things that
is created. In certain situations it may not be possible to correctly initialize a config mapping. For instance, if the
mapping requires values from a custom ConfigSource
. For this reason, any config mapping requires the annotation
@io.quarkus.runtime.configuration.StaticInitSafe
to mark the mapping as safe to be used at this stage. Learn more
about registration of a custom ConfigSource
.
Retrieval
可以将配置映射接口注入到任何 CDI 感知 Bean 中:
A config mapping interface can be injected into any CDI aware bean:
class BusinessBean {
@Inject
Server server;
public void businessMethod() {
String host = server.host();
}
}
在非 CDI 上下文中,使用 API io.smallrye.config.SmallRyeConfig#getConfigMapping
检索配置映射实例:
In non-CDI contexts, use the API io.smallrye.config.SmallRyeConfig#getConfigMapping
to retrieve the config mapping
instance:
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
Server server = config.getConfigMapping(Server.class);
Nested groups
嵌套映射提供了一种对其他配置属性进行子分组的方法:
A nested mapping provides a way to subgroup other config properties:
@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
映射组的方法名称充当配置属性的子名称空间。
The method name of a mapping group acts as sub-namespace to the configurations properties.
Overriding property names
@WithName
如果方法名称或属性名称彼此不匹配,`@WithName`注释可以覆盖方法名称映射并使用注释中提供的名称:
If a method name, or a property name do not match with each other, the @WithName
annotation can override the method
name mapping and use the name supplied in the annotation:
@ConfigMapping(prefix = "server")
public interface Server {
@WithName("name")
String host();
int port();
}
server.name=localhost
server.port=8080
@WithParentName
`@WithParentName`注释允许配置映射继承其容器名称,简化了与映射匹配所需的配置属性名称:
The @WithParentName
annotation allows to configurations mapping to inherit its container name, simplifying the
configuration property name required to match the mapping:
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
。
Without the @WithParentName
the method name()
requires the configuration property server.info.name
. Because we use
@WithParentName
, the info()
mapping will inherit the parent name from Server
and name()
maps to server.name
instead.
NamingStrategy
使用骆驼式命名法的方法名称映射为短横线分隔式属性名称:
Method names in camelCase map to kebab-case property names:
@ConfigMapping(prefix = "server")
public interface Server {
String theHost();
int thePort();
}
server.the-host=localhost
server.the-port=8080
可以通过在 `@ConfigMapping`注释中设置 `namingStrategy`值来调整映射策略:
The mapping strategy can be adjusted by setting namingStrategy
value in the @ConfigMapping
annotation:
@ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM)
public interface ServerVerbatimNamingStrategy {
String theHost();
int thePort();
}
server.theHost=localhost
server.thePort=8080
`@ConfigMapping`注释支持以下命名策略:
The @ConfigMapping
annotation support the following naming strategies:
-
KEBAB_CASE
(default) - The method name is derived by replacing case changes with a dash to map the configuration property. -
VERBATIM
- The method name is used as is to map the configuration property. -
SNAKE_CASE
- The method name is derived by replacing case changes with an underscore to map the configuration property.
Conversions
配置映射类支持自动转换 `Config`中可用转换的所有类型:
A config mapping class support automatic conversions of all types available for conversion in 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`及其支持的类型有效:
This is also valid for Optional
and friends:
@ConfigMapping
public interface Optionals {
Optional<Server> server();
Optional<String> optional();
@WithName("optional.int")
OptionalInt optionalInt();
interface Server {
String host();
int port();
}
}
在这种情况下,如果没有配置属性与映射匹配,映射不会失败。
In this case, the mapping won’t fail if there is no configuration property to match the mapping.
@WithConverter
`@WithConverter`注释提供了一种在特定映射中使用 `Converter`的方法:
The @WithConverter
annotation provides a way to set a Converter
to use in a specific mapping:
@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
。
A call to Converters.foo()
results in the value bar
.
Collections
配置映射还可以映射集合类型 List`和 `Set
:
A config mapping is also able to map collections types List
and 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
),其配置值是一个逗号分隔的字符串。
The List
or Set
mappings can use indexed properties to map
configuration values in mapping groups. For collection with simple element types like String
, their configuration
value is a comma separated string.
Maps
配置映射还可以映射 Map
:
A config mapping is also able to map a 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`的三元素。
The configuration property needs to specify an additional name to act as the key. In this case the form()
Map
will
contain three elements with the keys login-page
, error-page
and landing-page
.
它也适用于组:
It also works for groups:
@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`元素。
In this case the allServers()
Map
will
contain one Server
element with the key my-server
.
Defaults
`@WithDefault`注释允许将默认属性设置为映射(如果配置值在任何 `ConfigSource`中不可用,则阻止和错误):
The @WithDefault
annotation allows to set a default property into a mapping (and prevent and error if the
configuration value is not available in any ConfigSource
):
public interface Defaults {
@WithDefault("foo")
String foo();
@WithDefault("bar")
String bar();
}
不需要配置属性。Defaults.foo()`将返回值 `foo
,而 Defaults.bar()`将返回值 `bar
。
No configuration properties required. The Defaults.foo()
will return the value foo
and Defaults.bar()
will return
the value bar
.
Validation
配置映射可以组合来自 Bean Validation的注释来验证配置值:
A config mapping may combine annotations from Bean Validation to validate configuration values:
@ConfigMapping(prefix = "server")
public interface Server {
@Size(min = 2, max = 20)
String host();
@Max(10000)
int port();
}
为了使验证工作,`quarkus-hibernate-validator`扩展是必需的,并且会自动执行。
For validation to work, the quarkus-hibernate-validator
extension is required, and it is performed
automatically.
Mocking
映射接口实现不是代理,因此不能用 `@InjectMock`像其他 CDI bean一样直接模拟它。一种技巧是用生产者方法使它可代理:
A mapping interface implementation is not a proxy, so it cannot be mocked directly with @InjectMock
like other CDI
beans. One trick is to make it proxyable with a producer method:
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 测试类中作为模拟:
The Server
can be injected as a mock into a Quarkus test class with @InjectMock
:
@QuarkusTest
class ServerMockTest {
@InjectMock
Server server;
@Test
void localhost() {
Mockito.when(server.host()).thenReturn("localhost");
assertEquals("localhost", server.host());
}
}
模拟只是一个没有实际配置值的空壳。 |
The mock is just an empty shell without any actual configuration values. |
如果目标只是模拟某些配置值并保留原始配置,则模拟实例需要一个间谍:
If the goal is to only mock certain configuration values and retain the original configuration, the mocking instance requires a spy:
@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 测试类中作为模拟:
The AppConfig
can be injected as a mock into a Quarkus test class with @Inject
:
@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 单独监视。 |
Nested elements need to be spied individually by Mockito. |