Mapping configuration to objects
設定マッピングでは、同じプレフィックスを持つ複数の設定プロパティを1つのインターフェースにまとめることができます。
1. @ConfigMapping
設定マッピングでは、最小メタデータ設定で、 @io.smallrye.config.ConfigMapping のアノテーションが付いたインターフェースが必要です。
@ConfigMapping(prefix = "server")
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. 登録
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")
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")
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 のような単純な要素タイプを持つコレクションの場合、その設定値はコンマ区切りの文字列です。
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つの要素が含まれます。
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")
interface Server {
@Size(min = 2, max = 20)
String host();
@Max(10000)
int port();
}
For validation to work, the quarkus-hibernate-validator extension is required, and it is performed
automatically.
|
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());
}
}
| The mock is just an empty shell without any actual configuration values. |
特定の設定値のみをモックし、元の設定を保持することが目的の場合、モックインスタンスにはスパイが必要となります。
@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());
}
}
| Nested elements need to be spied individually by Mockito. |
2. @ConfigProperties (Deprecated)
This feature will be removed soon, please update your code base and use @ConfigMapping instead.
|
@io.quarkus.arc.config.ConfigProperties アノテーションは、複数の関連する設定値を独自のクラスにまとめることができます。
package org.acme.config;
import io.quarkus.arc.config.ConfigProperties;
import java.util.Optional;
@ConfigProperties(prefix = "greeting") (1)
public class GreetingConfiguration {
private String message;
private String suffix = "!"; (2)
private Optional<String> name;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public Optional<String> getName() {
return name;
}
public void setName(Optional<String> name) {
this.name = name;
}
}
| 1 | prefix は任意です。設定していない場合は、使用される接頭辞はクラス名によって決まります。この場合も (Configuration の接尾辞は削除されているため) greeting となります。クラス名が GreetingExtraConfiguration の場合は、デフォルトの接頭辞が greeting-extra となります。 |
| 2 | ! は、 greeting.suffix が設定されていない場合のデフォルト値になります。 |
CDI @Inject で GreetingResource を注入してください。
@Inject
GreetingConfiguration greetingConfiguration;
Quarkusが提供するもう一つの代替スタイルは、インターフェイスとして GreetingConfiguration を作成することです。
package org.acme.config;
import io.quarkus.arc.config.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.Optional;
@ConfigProperties(prefix = "greeting")
public interface GreetingConfiguration {
@ConfigProperty(name = "message") (1)
String message();
@ConfigProperty(defaultValue = "!")
String getSuffix(); (2)
Optional<String> getName(); (3)
}
| 1 | @ConfigProperty アノテーションが必要なのは、メソッドが対応する設定プロパティの名前が、ゲッターメソッドの命名規則に従っていないからです。 |
| 2 | この場合、 name が設定されていないので、対応するプロパティは greeting.suffix となります。 |
| 3 | メソッド名はゲッターメソッドの命名規則( greeting.name が対応するプロパティ)に従っており、デフォルト値は必要ないため、 @ConfigProperty アノテーションを指定する必要はありません。 |
クラスやインターフェイスで @ConfigProperties を使用する場合、そのフィールドの 1 つの値が提供されていないと、アプリケーションの起動に失敗し、 javax.enterprise.inject.spi.DeploymentException がスローされます。これは、 Optional フィールドとデフォルト値を持つフィールドには適用されません。
2.1. @ConfigProperties に関する追加の注意事項
@ConfigProperties でアノテーションされた通常のクラスを使用する場合、クラスは必ずしも getter および setter を宣言する必要はありません。単純でパブリックな確定的でないフィールドを持つことも有効です。
さらに、設定クラスはネストされたオブジェクト設定をサポートしています。例えば、 content という名前のグリーティングの設定を追加で用意する必要があり、その中にはいくつかのフィールドが含まれているとします。
@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {
public String message;
public String suffix = "!";
public Optional<String> name;
public ContentConfig content; (1)
public static class ContentConfig {
public Integer prizeAmount;
public List<String> recipients;
}
}
| 1 | フィールドの名前 (クラス名ではありません) は、オブジェクトにバインドされるプロパティーの名前を決定します。 |
プロパティの設定は通常の方法で行います。
greeting.message = hello
greeting.name = quarkus
greeting.content.prize-amount=10
greeting.content.recipients=Jane,John
さらに、 @ConfigProperties でアノテーションされたクラスには、Bean Validationアノテーションを付けることができます。
@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {
@Size(min = 20)
public String message;
public String suffix = "!";
}
For validation to work, the quarkus-hibernate-validator extension is required.
|
2.2. 同じ ConfigProperties で異なる接頭辞を使用
また、Quarkusでは、 io.quarkus.arc.config.@ConfigPrefix アノテーションを使用して、同じ @ConfigProperties オブジェクトを注入ポイントごとに異なるプレフィックスで使用することもサポートしています。上記の GreetingConfiguration が、 greeting のプレフィックスと other のプレフィックスの両方に使用される必要がある場合は以下のようにします。
@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {
@Size(min = 20)
public String message;
public String suffix = "!";
}
@ApplicationScoped
public class SomeBean {
@Inject (1)
GreetingConfiguration greetingConfiguration;
@ConfigPrefix("other") (2)
GreetingConfiguration otherConfiguration;
}
| 1 | この注入ポイントでは、 greetingConfiguration は @ConfigProperties で定義されている greeting の接頭辞を使用します。 |
| 2 | この注入ポイントでは、 otherConfiguration は、 greeting の接頭辞の代わりに @ConfigPrefix から other の接頭辞を使用します。この場合、 @Inject は必須ではないことに注意してください。 |
2.3. ConfigPropertiesとビルド時条件の組み合わせ
Quarkusでは、ビルド時に評価される条件( @IfBuildProfile 、 @UnlessBuildProfile 、 @IfBuildProperty 、 @UnlessBuildProperty )を定義して、アノテーション @ConfigProperties と @ConfigPrefix を有効にしたり、無効にしたりすることができ、構成を非常に柔軟にマッピングすることができます。
サービスの設定が @ConfigProperties にマッピングされていて、テストではこの設定部分はモックされるので必要ないとします。その場合は、次の例のようにビルド時の条件を定義します。
ServiceConfiguration.java
@UnlessBuildProfile("test") (1)
@ConfigProperties
public class ServiceConfiguration {
public String user;
public String password;
}
| 1 | アノテーション @ConfigProperties は、アクティブなプロファイルが test でない場合にのみ考慮されます。 |
SomeBean.java
@ApplicationScoped
public class SomeBean {
@Inject
Instance<ServiceConfiguration> serviceConfiguration; (1)
}
| 1 | サービス設定が欠落している可能性があるため、インジェクションポイントで Instance<ServiceConfiguration> を型として使用する必要があります。 |