The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

設定をオブジェクトにマッピング

設定マッピングでは、同じプレフィックスを持つ複数の設定プロパティを1つのインターフェースにまとめることができます。

1. @ConfigMapping

設定マッピングでは、最小メタデータ設定で、 @io.smallrye.config.ConfigMapping のアノテーションが付いたインターフェースが必要です。

@ConfigMapping(prefix = "server")
interface Server {
    String host();

    int port();
}

Server インターフェースは、 server.host という名前の設定プロパティを Server.host() メソッドに、 server.portServer.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.1.1.1. 例
@StaticInitSafe
@ConfigMapping(prefix = "server")
interface Server {
    String host();

    int port();
}

1.1.2. RUNTIME INIT

RUNTIME INIT ステージは、 STATIC INIT の後に起こります。この段階では何の制限もなく、あらゆる設定マッピングが期待通りに Config インスタンスに追加されます。

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();
    }
}
application.properties
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();
}
application.properties
server.name=localhost
server.port=8080

1.4.2. @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();
}
application.properties
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();
}
application.properties
server.the-host=localhost
server.the-port=8080

マッピング戦略は、 @ConfigMapping のアノテーションで namingStrategy の値を設定することで調整できます。

@ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM)
public interface ServerVerbatimNamingStrategy {
    String theHost();

    int thePort();
}
application.properties
server.theHost=localhost
server.thePort=8080

The @ConfigMapping annotation support the following naming strategies:

  • 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();
}
application.properties
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();
    }
}

In this case, the mapping won’t fail if there is no configuration property to match the mapping.

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";
    }
}
application.properties
foo=foo

Converters.foo() を呼び出すと、 bar という値が得られます。

1.5.2. コレクション

また、設定マッピングは、コレクションタイプ ListSet をマッピングすることができます。

@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();
        }
    }
}
application.properties
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

ListSet のマッピングでは、 インデックス付きのプロパティを使用して、マッピンググループの設定値をマッピングすることができます。 String のような単純な要素タイプを持つコレクションの場合、その設定値はコンマ区切りの文字列です。

1.5.3. マップ

また、設定マッピングは、 Map をマッピングすることができます。

@ConfigMapping(prefix = "server")
public interface Server {
    String host();

    int port();

    Map<String, String> form();
}
application.properties
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();
}

No configuration properties required. The Defaults.foo() will return the value foo and Defaults.bar() will return the value bar.

1.7. バリデーション

設定マッピングは、設定値を検証するために Bean Validationからのアノテーションを組み合わせることができます。

@ConfigMapping(prefix = "server")
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が個別にスパイする必要があります。

2. @ConfigProperties (Deprecated)

この機能はまもなく削除される予定です。コードベースを更新し、代わりに @ConfigMapping を使用してください。

@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 @InjectGreetingResource を注入してください。

@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 を宣言する必要はありません。単純でパブリックな final でないフィールドを持つことも有効です。

さらに、設定クラスはネストされたオブジェクト設定をサポートしています。例えば、 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 フィールドの名前 (クラス名ではありません) は、オブジェクトにバインドされるプロパティーの名前を決定します。

プロパティの設定は通常の方法で行います。

application.properties
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 = "!";

}
検証が機能するためには、 quarkus-hibernate-validator のエクステンションが必要です。

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> を型として使用する必要があります。