Mapping Configuration to Objects
設定マッピングでは、同じプレフィックスを持つ複数の設定プロパティを1つのインターフェースにまとめることができます。
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() メソッドにマッピングすることができます。検索する設定プロパティ名は、プレフィックスと、 . (ドット)をセパレータとするメソッド名から構築されます。
If a mapping fails to match a configuration property a NoSuchElementException is thrown, unless the mapped
element is an Optional.
|
1.1. Mapping Rules
A config mapping interface must obey the following rules:
-
A mapping method cannot accept parameters
-
A mapping method return type cannot be
void -
A mapping cannot use self-reference types
-
defaultmethods are allowed
1.2. 登録
Quarkusアプリケーションの起動時に、コンフィグマッピングを2回登録することができます。1回は _STATIC INIT_用、2回目は _RUNTIME INIT_用です。
1.2.1. STATIC INIT
Quarkusは静的初期化中にいくつかのサービスを開始しますが、 Config は通常、最初に作成されるものの1つです。状況によっては、設定マッピングを正しく初期化できない場合があります。例えば、マッピングがカスタム ConfigSource からの値を必要とする場合などです。このため、どのようなコンフィグマッピングでも、この段階でマッピングを安全に使用できるとマークするには、アノテーション @io.quarkus.runtime.configuration.StaticInitSafe が必要になります。カスタム ConfigSource の 登録についてはこちらをご覧ください。
1.3. 取得
設定マッピングインタフェースは,任意の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.4. Hierarchy
A config mapping can extend another mapping and inherit all its super members:
public interface Parent {
String name();
}
@ConfigMapping(prefix = "child")
public interface Child extends Parent {
}
Members can also be overridden:
public interface Parent {
String name();
}
@ConfigMapping(prefix = "child")
public interface Child extends Parent {
@WithName("child-name")
String name();
}
1.5. ネストされたグループ
ネストされたマッピングは、他の設定プロパティをサブグループ化する方法を提供します。
@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.6. プロパティ名のオーバーライド
1.6.1. @WithName
メソッド名やプロパティ名が互いに一致しない場合、 @WithName アノテーションはメソッド名のマッピングを上書きし、アノテーションで提供された名前を使用することができます。
@ConfigMapping(prefix = "server")
public interface Server {
@WithName("name")
String host();
int port();
}
server.name=localhost
server.port=8080
1.6.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.6.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.6.4. beanStyleGetters
The beanStyleGetters attribute (default false) enables matching bean-style getter names (get/is prefixed) to
their property name equivalent. For example, getHost() and isEnabled() map to the properties host and enabled
respectively:
@ConfigMapping(prefix = "server", beanStyleGetters = true)
public interface Server {
String getHost();
int getPort();
boolean isEnabled();
}
server.host=localhost
server.port=8080
server.enabled=true
Bean-style getter matching allows multiple method names to match the same configuration name. For instance,
getFoo and isFoo both match foo, which may not be intended. Prefer simple method names that match
one-to-one with their configuration names.
|
1.7. 変換
設定マッピングクラスは、 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 と friends にも有効です。
@ConfigMapping
public interface Optionals {
Optional<Server> server();
Optional<String> optional();
@WithName("optional.int")
OptionalInt optionalInt();
interface Server {
String host();
int port();
}
}
この場合、マッピングにマッチする設定プロパティがなければ、マッピングは失敗しません。
1.7.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.7.2. Optionals
A mapping can wrap any complex type with an Optional. Optional mappings do not require the configuration path
and value to be present, so no NoSuchElementException is thrown when the configuration property is missing.
1.7.3. コレクション
また、設定マッピングは、コレクションタイプ 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 のような単純な要素タイプを持つコレクションの場合、その設定値はコンマ区切りの文字列です。
A List mapping is backed by an ArrayList, and a Set mapping is backed by a HashSet. Only the List
mapping can maintain element order.
|
1.7.4. マップ
また、設定マッピングは、 Map をマッピングすることができます。
@ConfigMapping(prefix = "server")
public interface Server {
String host();
int port();
Map<String, String> form();
Map<String, List<Alias>> aliases();
interface Alias {
String name();
}
}
server.host=localhost
server.port=8080
server.form.index=index.html
server.form.login.page=login.html
server.form.error.page=error.html
server.aliases.localhost[0].name=prod
server.aliases.localhost[1].name=127.0.0.1
server.aliases."io.quarkus"[0].name=quarkus
The configuration property needs to specify an additional segment to act as the map key. In this case the form() Map
will contain three elements with the keys index, login.page and error.page.
Quotes are required around a Map key only when the key contains a dot (e.g. "io.quarkus"), because a dot
would otherwise be interpreted as a path separator. For single-segment keys (no dots), quotes are optional.
|
|
Do not mix quoted and unquoted forms for the same When both quoted and unquoted forms are found, only the quoted form is then used to look up values and; property names written in the unquoted form are left unmapped and the mapping may fail validation or yield unexpected values. If members of a nested group are split between the two forms, the unquoted members are not found and the mapping may fail validation or yield unexpected values. Pick one form and use it consistently for a given key across all configuration sources. |
グループでも有効です:
@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 要素が1つ含まれます。
1.7.5. @WithUnnamedKey
The @WithUnnamedKey annotation allows omitting a single map key in the configuration path:
@ConfigMapping(prefix = "server")
public interface Server {
@WithUnnamedKey("localhost")
Map<String, Alias> aliases();
interface Alias {
String name();
}
}
server.aliases.name=localhost
server.aliases.prod.name=prod
The server.aliases.name property is unnamed because it does not contain the map key segment. Due to
@WithUnnamedKey("localhost"), the key localhost is used automatically when the map key is absent.
Server server = config.getConfigMapping(Server.class);
Alias localhost = server.aliases().get("localhost");
Alias prod = server.aliases().get("prod");
If the unnamed key is also explicitly set in a property name (e.g. server.aliases.localhost.name=explicit), the
explicit value takes precedence over the unnamed entry.
The eager attribute (default true) controls whether the unnamed key entry is included when its values come only
from defaults. When eager = false, the entry is excluded from the Map unless at least one value is explicitly
set in a configuration source.
1.7.6. @WithKeys
The @WithKeys annotation defines which Map keys must be loaded by the configuration, instead of discovering keys
from Config#getPropertyNames. This is useful when the ConfigSource does not enumerate its properties:
@ConfigMapping(prefix = "server")
public interface Server {
@WithKeys(KeysProvider.class)
Map<String, Alias> aliases();
interface Alias {
String name();
}
class KeysProvider implements Supplier<Iterable<String>> {
@Override
public Iterable<String> get() {
return List.of("dev", "test", "prod");
}
}
}
Each key must exist in the final configuration relative to the Map path segment, or the mapping will fail with a
ConfigValidationException.
1.7.7. @WithDefaults
The @WithDefaults marker annotation on a Map returns the default value for the value element on any key lookup:
@ConfigMapping(prefix = "server")
public interface Server {
@WithDefaults
Map<String, Alias> aliases();
interface Alias {
@WithDefault("localhost")
String name();
}
}
server.aliases.prod.name=prod
A lookup with the key localhost, any, or any other key returns an Alias instance populated from @WithDefault
values. A lookup with prod returns an Alias instance with name=prod because the property is defined in the
configuration. The Map can only iterate and size explicitly defined keys — in this case only prod.
Server server = config.getConfigMapping(Server.class);
Alias localhost = server.aliases().get("localhost"); (1)
Alias any = server.aliases().get("any"); (2)
Alias prod = server.aliases().get("prod"); (3)
| 1 | Calling localhost.name() returns localhost |
| 2 | Calling any.name() also returns localhost, since it is the default |
| 3 | Calling prod.name() return prod, since it is the value defined in the configuration file |
1.8. デフォルト
@WithDefault アノテーションにより、デフォルトのプロパティをマッピングに設定することができます(また、設定値がどの ConfigSource においても利用できない場合はエラーになりません)。
public interface Defaults {
@WithDefault("foo")
String foo();
@WithDefault("bar")
String bar();
}
設定プロパティは必要ありません。 Defaults.foo() は値 foo を、 Defaults.bar() は値 bar を返します。
1.9. Secrets
A mapping can mark a member as a secret with Secret<T>:
@ConfigMapping(prefix = "credentials")
public interface Credentials {
String username();
Secret<String> password();
}
A Secret value modifies the behavior of the Config system by:
-
Omitting the name of the secret from
Config#getPropertyNames() -
Omitting the name and value of the secret from the mapping
toStringoutput -
Throwing a
SecurityExceptionwhen trying to retrieve the value via theConfigprogrammatic API
A Secret can be of any type that can be converted by a registered org.eclipse.microprofile.config.spi.Converter
of the same type.
1.10. toString, equals, hashCode
If the config mapping contains a toString method declaration, the config mapping instance will include a proper
implementation of the toString method. The equals and hashCode methods are included automatically.
Do not include a toString declaration in a config mapping with sensitive information.
|
1.11. バリデーション
設定マッピングは、設定値を検証するために Bean Validationからのアノテーションを組み合わせることができます。
@ConfigMapping(prefix = "server")
public interface Server {
@Size(min = 2, max = 20)
String host();
@Max(10000)
int port();
}
The application startup fails with a io.smallrye.config.ConfigValidationException if the configuration property
values do not follow the constraints defined in Server.
For validation to work, the quarkus-hibernate-validator extension is required, and it is performed
automatically.
|
1.12. モック
マッピングインターフェースの実装はプロキシではありませんので、他の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 は、モックとして @InjectMock でQuarkusのテストクラスに注入することができます。
@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. |
特定の設定値のみをモックし、元の設定を保持することが目的の場合、モックインスタンスにはスパイが必要となります。
@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 は、モックとして @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());
}
}
| Nested elements need to be spied individually by Mockito. |
2. ConfigInstanceBuilder
With the io.smallrye.config.ConfigInstanceBuilder API, it is possible to create instances of a config mapping
interface programmatically, without requiring a SmallRyeConfig instance or any configuration source. This is
particularly useful for testing, providing default configurations, or any scenario where configuration values are
known ahead of time.
The configuration interface does not need the @ConfigMapping annotation to work with the builder. Any valid
configuration interface is accepted.
|
2.1. 使用方法
A configuration interface instance is created with ConfigInstanceBuilder.forInterface():
interface Server {
String host();
int port();
}
Server server = ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.with(Server::port, 8080)
.build();
The builder uses method references to identify which property to set, providing compile-time type safety without string-based property names.
2.2. Primitive Types
The builder provides dedicated with() overloads for int, long, double, and boolean. Other primitive types
(byte, short, float, char) use the generic with() method with their boxed types:
@ConfigMapping
interface Primitives {
int intValue();
boolean booleanValue();
byte byteValue();
}
Primitives primitives = ConfigInstanceBuilder.forInterface(Primitives.class)
.with(Primitives::intValue, 42)
.with(Primitives::booleanValue, true)
.with(Primitives::byteValue, Byte.valueOf((byte) 1))
.build();
2.3. Optional Properties
The withOptional() method sets Optional properties. If an optional property is not set, it defaults to empty:
interface AppConfig {
Optional<String> name();
OptionalInt timeout();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.withOptional(AppConfig::name, "MyApp")
.withOptional(AppConfig::timeout, 30)
.build();
The withOptional method wraps the value in Optional.of(), OptionalInt.of(), OptionalLong.of(), or
OptionalDouble.of() depending on the property type.
2.4. デフォルト
Properties annotated with @WithDefault are automatically applied when no value is explicitly set in the builder:
interface ServerDefaults {
@WithDefault("localhost")
String host();
@WithDefault("8080")
int port();
}
ServerDefaults server = ConfigInstanceBuilder.forInterface(ServerDefaults.class).build(); (1)
| 1 | Both host() and port() return their @WithDefault values |
Explicitly set values override the @WithDefault annotation:
ServerDefaults server = ConfigInstanceBuilder.forInterface(ServerDefaults.class)
.with(ServerDefaults::host, "0.0.0.0")
.build(); (1)
| 1 | Now, host() returns 0.0.0.0 and, port() returns 8080 from @WithDefault |
2.5. Nested Groups
Nested configuration groups are built separately and composed into the parent builder:
interface AppConfig {
String name();
DatabaseConfig database();
}
interface DatabaseConfig {
String url();
int poolSize();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.with(AppConfig::name, "MyApp")
.with(AppConfig::database, ConfigInstanceBuilder.forInterface(DatabaseConfig.class)
.with(DatabaseConfig::url, "jdbc:h2:mem:test")
.with(DatabaseConfig::poolSize, 10)
.build())
.build();
If a nested group has @WithDefault values for all its members, the nested group instance is automatically
built with those defaults when not explicitly set in the parent builder.
|
2.6. Collections and Maps
List, Set, and Map types are set directly with their values:
interface AppConfig {
List<String> hosts();
Map<String, String> labels();
}
AppConfig config = ConfigInstanceBuilder.forInterface(AppConfig.class)
.with(AppConfig::hosts, List.of("host1", "host2"))
.with(AppConfig::labels, Map.of("env", "prod", "region", "us-east"))
.build();
2.7. Required Properties
Properties without a @WithDefault are considered required. Calling build() throws a NoSuchElementException if
any required property is not set:
interface Server {
String host();
@WithDefault("8080")
int port();
}
Server fails = ConfigInstanceBuilder.forInterface(Server.class)
.build(); (1)
Server works = ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.build(); (2)
| 1 | - Throws a NoSuchElementException, the host is required but not set |
| 2 | - Works as expected since the host value is now provided |
2.8. Builder Reuse
A builder instance can be used to produce multiple independent instances. Each build() call creates a new object:
ConfigInstanceBuilder<Server> builder = ConfigInstanceBuilder.forInterface(Server.class)
.with(Server::host, "localhost")
.with(Server::port, 8080);
Server first = builder.build();
Server second = builder.build();
boolean equals = first.equals(second); (1)
| 1 | first and second are equal but not the same object |