将配置映射到对象
通过配置映射,可以将多个具有相同前缀的配置属性分组到一个接口中。
1. @ConfigMapping
配置映射需要一个公共接口,其中包含最少的元数据配置,并用 @io.smallrye.config.ConfigMapping
注解进行标注。
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
}
Server
接口能够将名为 server.host
的配置属性映射到 Server.host()
方法,将 server.port
映射到 Server.port()
方法。要查找的配置属性名称是通过前缀和方法名称,以 .
(点) 作为分隔符构建的。
如果映射未能匹配到配置属性,则会抛出 NoSuchElementException ,除非映射的元素是 Optional 。 |
1.1. 注册
当 Quarkus 应用程序启动时,配置映射可以注册两次。一次用于静态初始化,一次用于运行时初始化。
1.1.1. 静态初始化
Quarkus 在静态初始化期间启动一些服务,而 Config
通常是最先创建的东西之一。在某些情况下,可能无法正确初始化配置映射。例如,如果映射需要来自自定义 ConfigSource
的值。因此,任何配置映射都需要 @io.quarkus.runtime.configuration.StaticInitSafe
注解来标记该映射在此阶段是安全的。了解更多关于自定义 ConfigSource
的注册。
1.2. 检索
配置映射接口可以注入到任何支持 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);
1.3. 嵌套分组
嵌套映射提供了一种对其他配置属性进行分组的方法。
@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
映射组的方法名充当配置属性的子命名空间。
1.4. 覆盖属性名
1.4.1. @WithName
如果方法名或属性名不匹配,@WithName
注解可以覆盖方法名映射,并使用注解中提供的名称。
@ConfigMapping(prefix = "server")
public interface Server {
@WithName("name")
String host();
int port();
}
server.name=localhost
server.port=8080
1.4.2. @WithParentName
@WithParentName
注解允许配置映射属性继承其容器名称,从而简化了匹配映射所需的配置属性名称。
@ConfigMapping(prefix = "server")
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
。
1.4.3. 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
(默认)- 方法名通过将大小写变化替换为连字符来映射配置属性,即theHost
映射到the-host
。 -
VERBATIM
- 方法名按原样使用以映射配置属性,即theHost
映射到theHost
。 -
SNAKE_CASE
- 方法名通过将大小写变化替换为下划线来映射配置属性,即theHost
映射到the_host
。
1.5. 转换
配置映射类支持 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();
}
}
在这种情况下,如果映射没有匹配的配置属性,映射也不会失败。
1.5.1. @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
的值。
1.5.2. 集合
配置映射也能够映射集合类型 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
映射可以使用索引属性来映射映射组中的配置值。对于具有简单元素类型(如 String
)的集合,其配置值是以逗号分隔的字符串。
只有 List 映射可以保持元素顺序。因此,对于 Set 映射,元素顺序不从配置文件维护,而是随机的。 |
1.5.3. Map
配置映射也能够映射 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
将包含一个 Server
元素,键为 my-server
。
1.6. 默认值
@WithDefault
注解允许为映射设置默认属性(并防止在任何 ConfigSource
中都找不到配置值时出错)。
public interface Defaults {
@WithDefault("foo")
String foo();
@WithDefault("bar")
String bar();
}
不需要任何配置属性。Defaults.foo()
将返回 foo
的值,Defaults.bar()
将返回 bar
的值。
1.7. 验证
配置映射可以结合Bean Validation 的注解来验证配置值。
@ConfigMapping(prefix = "server")
public interface Server {
@Size(min = 2, max = 20)
String host();
@Max(10000)
int port();
}
为了使验证生效,需要 quarkus-hibernate-validator 扩展,并且它会自动执行。 |
1.8. 模拟
映射接口的实现不是代理,因此不能像其他 CDI Bean 一样用 @InjectMock
直接模拟。一种技巧是使用生产者方法使其可代理。
public class ServerMockProducer {
@Inject
Config config;
@Produces
@ApplicationScoped
@io.quarkus.test.Mock
Server server() {
return config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class);
}
}
Server
可以作为模拟对象注入到 Quarkus 测试类中,使用 @InjectMock
。
@QuarkusTest
class ServerMockTest {
@InjectMock
Server server;
@Test
void localhost() {
Mockito.when(server.host()).thenReturn("localhost");
assertEquals("localhost", server.host());
}
}
模拟对象只是一个空壳,没有任何实际的配置值。 |
如果目标是只模拟某些配置值并保留原始配置,则模拟实例需要一个 spy。
@ConfigMapping(prefix = "app")
@Unremovable
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
可以作为模拟对象注入到 Quarkus 测试类中,使用 @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 进行 spy。 |