設定をオブジェクトにマッピング
設定マッピングでは、同じプレフィックスを持つ複数の設定プロパティを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()
メソッドにマッピングすることができます。検索する設定プロパティ名は、プレフィックスと、 .
(ドット)をセパレータとするメソッド名から構築されます。
マッピングが設定プロパティと一致しない場合、マッピングされた要素が Optional でない限り、 NoSuchElementException がスローされます。
|
1.1. 登録
Quarkusアプリケーションの起動時に、コンフィグマッピングを2回登録することができます。1回は _STATIC INIT_用、2回目は _RUNTIME INIT_用です。
1.1.1. STATIC INIT
Quarkusは静的初期化中にいくつかのサービスを開始しますが、 Config
は通常、最初に作成されるものの1つです。状況によっては、設定マッピングを正しく初期化できない場合があります。例えば、マッピングがカスタム 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
アノテーションを使用すると、設定マッピングがそのコンテナー名を継承できるようになり、マッピングに一致させるために必要な設定プロパティ名が簡素化されます:
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. ネーミング戦略
キャメルケースのメソッド名は、ケバブケースのプロパティ名にマッピングされます。
@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
- メソッド名は、設定プロパティをマッピングするためにケースの変更をアンダースコアで置き換えて導き出されます。
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
と friends にも有効です。
@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
のような単純な要素タイプを持つコレクションの場合、その設定値はコンマ区切りの文字列です。
Only the List mapping can maintain element order. Hence, with Set mappings the element order is not maintained from the configuration files but is random.
|
1.5.3. マップ
また、設定マッピングは、 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
というキーを持つ3つの要素が含まれます。
グループでも有効です:
@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.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
は、モックとして @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が個別にスパイする必要があります。 |