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")
public 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")
public 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")
public interface Server {
    @WithName("name")
    String host();

    int port();
}
application.properties
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();
}
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")
public 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

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

この場合、マッピングにマッチする設定プロパティがなければ、マッピングは失敗しません。

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つの要素が含まれます。

It also works for groups:

@ConfigMapping(prefix = "server")
public interface Servers {
    @WithParentName
    Map<String, Server> allServers();
}

public interface Server {
    String host();

    int port();

    String login();

    String error();

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

In this case the allServers() Map will contain one Server element with the key 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 は、モックとして @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が個別にスパイする必要があります。

関連コンテンツ