Extending Configuration Support

Custom ConfigSource

可以创建自定义的 ConfigSource,如 MicroProfile Config 中所述。

有了自定义的 ConfigSource,就能读取附加的配置值,并将它们按照定义的序数添加到 Config 实例中。这允许从其他来源覆盖值,或回退到其他值。

config sources

一个自定义 ConfigSource 需要对 org.eclipse.microprofile.config.spi.ConfigSourceorg.eclipse.microprofile.config.spi.ConfigSourceProvider 进行实现。每个实现都要求通过 ServiceLoader 机制进行注册,无论是在 META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource 还是 META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider 文件中。

Example

考虑一个简单的内存中 ConfigSource

org.acme.config.InMemoryConfigSource
package org.acme.config;

import org.eclipse.microprofile.config.spi.ConfigSource;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class InMemoryConfigSource implements ConfigSource {
    private static final Map<String, String> configuration = new HashMap<>();

    static {
        configuration.put("my.prop", "1234");
    }

    @Override
    public int getOrdinal() {
        return 275;
    }

    @Override
    public Set<String> getPropertyNames() {
        return configuration.keySet();
    }

    @Override
    public String getValue(final String propertyName) {
        return configuration.get(propertyName);
    }

    @Override
    public String getName() {
        return InMemoryConfigSource.class.getSimpleName();
    }
}

并在以下文件中注册:

META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
org.acme.config.InMemoryConfigSource

InMemoryConfigSource 将在 .env 源和 application.properties 源之间进行排序,这是由于 275 序数:

ConfigSource Ordinal

System Properties

400

Environment Variables from System

300

来自 .env 文件的环境变量

295

InMemoryConfigSource

275

application.properties from /config

260

application.properties from application

250

microprofile-config.properties from application

100

在此情况下,只有当配置引擎无法按照此顺序在 System Properties,Environment Variables from SystemEnvironment Variables from .env file 中找到值时,才将使用来自 InMemoryConfigSourcemy.prop

ConfigSource Init

当 Quarkus 应用程序启动时,可以初始化 ConfigSource 两次。一次针对 STATIC INIT,第二次针对 RUNTIME INIT

STATIC INIT

Quarkus 在静态初始化期间启动其某些服务,而 Config 通常是最先创建的东西之一。在某些情况下,可能无法添加自定义 ConfigSource。例如,如果 ConfigSource 要求其他服务,比如数据库访问,它在这个阶段将不可用,从而导致先有鸡还是先有蛋的问题。因此,任何自定义的 ConfigSource 都需要注释 @io.quarkus.runtime.configuration.StaticInitSafe,以将源标记为在此阶段使用时是安全的。

Example

考虑:

org.acme.config.InMemoryConfigSource
package org.acme.config;

import org.eclipse.microprofile.config.spi.ConfigSource;
import io.quarkus.runtime.annotations.StaticInitSafe;

@StaticInitSafe
public class InMemoryConfigSource implements ConfigSource {

}

并在以下文件中注册:

META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
org.acme.config.InMemoryConfigSource

InMemoryConfigSource 可以在 STATIC INIT 期间提供。

定制的 ConfigSource 不会在 Quarkus STATIC INIT 期间自动添加。需要用 @io.quarkus.runtime.configuration.StaticInitSafe 注解标注。

RUNTIME INIT

ConfigSource`可以在 RUNTIME INIT 阶段后重新初始化。此阶段没有限制,定制源如期添加至 `Config 实例。

ConfigSourceFactory

创建 ConfigSource 的另一种方法是通过 SmallRye Config io.smallrye.config.ConfigSourceFactory API。 SmallRye Config 工厂和 ConfigSource 创建标准方法之间的差别在于,工厂的能力为上下文提供对可用配置的访问。

每个 io.smallrye.config.ConfigSourceFactory 实现都要求通过 ServiceLoader 机制在 META-INF/services/io.smallrye.config.ConfigSourceFactory 文件中进行注册。

Example

考虑:

org.acme.config.URLConfigSourceFactory
package org.acme.config;

import java.util.Collections;
import java.util.OptionalInt;

import org.eclipse.microprofile.config.spi.ConfigSource;

import io.smallrye.config.ConfigSourceContext;
import io.smallrye.config.ConfigSourceFactory;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.PropertiesConfigSource;

public class URLConfigSourceFactory implements ConfigSourceFactory {
    @Override
    public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext context) {
        final ConfigValue value = context.getValue("config.url");
        if (value == null || value.getValue() == null) {
            return Collections.emptyList();
        }

        try {
            return Collections.singletonList(new PropertiesConfigSource(new URL(value.getValue())));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public OptionalInt getPriority() {
        return OptionalInt.of(290);
    }
}

并在以下文件中注册:

META-INF/services/io.smallrye.config.ConfigSourceFactory
org.acme.config.URLConfigSourceFactory

通过实现 io.smallrye.config.ConfigSourceFactory,一个 ConfigSource 列表可以通过 Iterable<ConfigSource> getConfigSources(ConfigSourceContext context) 方法提供。ConfigSourceFactory 通过覆盖方法 OptionalInt getPriority() 同样可以指定优先顺序。优先顺序值用于对多个 io.smallrye.config.ConfigSourceFactory (如果找到)排序。

io.smallrye.config.ConfigSourceFactory 优先顺序不影响 ConfigSource 顺序。这些内容会独立排序。

在工厂初始化时,提供的 ConfigSourceContext 可能调用方法 ConfigValue getValue(String name)。此方法会在所有 ConfigSource`s that were already initialized by the `Config 实例中查找配置名称,包括比 ConfigSourceFactory 中定义的序号低的源。ConfigSourceFactory 提供的 ConfigSource 列表不会被考虑用于配置其他由低优先级 ConfigSourceFactory 提供的源。

Custom Converter

可以按照 MicroProfile Config 指定创建一个定制的 Converter 类型。

定制的 Converter 需要实现 org.eclipse.microprofile.config.spi.Converter<T>。每个实现都需要通过 ServiceLoader 机制在 META-INF/services/org.eclipse.microprofile.config.spi.Converter 文件中进行注册。考虑:

package org.acme.config;

public class MicroProfileCustomValue {

    private final int number;

    public MicroProfileCustomValue(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }
}

相应的转换器会类似于以下内容。

package org.acme.config;

import org.eclipse.microprofile.config.spi.Converter;

public class MicroProfileCustomValueConverter implements Converter<MicroProfileCustomValue> {

    @Override
    public MicroProfileCustomValue convert(String value) {
        return new MicroProfileCustomValue(Integer.parseInt(value));
    }
}

定制转换器类必须是 public,必须具有不带参数的 public 构造函数,而且不能是 abstract

定制配置类型自动地转换配置值:

@ConfigProperty(name = "configuration.value.name")
MicroProfileCustomValue value;

Converter priority

jakarta.annotation.Priority 注解覆盖了 Converter 优先顺序并且更改转换器优先级以微调执行顺序。默认情况下,如果 Converter 没有指定 @Priority,则转换器以 100 的优先级注册。考虑:

package org.acme.config;

import jakarta.annotation.Priority;
import org.eclipse.microprofile.config.spi.Converter;

@Priority(150)
public class MyCustomConverter implements Converter<MicroProfileCustomValue> {

    @Override
    public MicroProfileCustomValue convert(String value) {

        final int secretNumber;
        if (value.startsFrom("OBF:")) {
            secretNumber = Integer.parseInt(SecretDecoder.decode(value));
        } else {
            secretNumber = Integer.parseInt(value);
        }

        return new MicroProfileCustomValue(secretNumber);
    }
}

因为它转换同样的值类型(MicroProfileCustomValue)并且具有 150 的优先级,因此它将替代具有 100 默认优先级的 MicroProfileCustomValueConverter

所有 Quarkus 核心转换器都使用 200 的优先级值。为了覆盖任何 Quarkus 特定的转换器,则优先级值应高于 200

Config Interceptors

SmallRye Config 提供了一个拦截器链,钩入配置值解析。这对于实现像 Profiles Property Expressions 那样的功能非常有用,或者仅仅通过日志来找出配置值是从哪里加载的。

拦截器要求实现 io.smallrye.config.ConfigSourceInterceptor。每个实现都需要通过 ServiceLoader 机制在 META-INF/services/io.smallrye.config.ConfigSourceInterceptor 文件中进行注册。

io.smallrye.config.ConfigSourceInterceptor 能够使用 ConfigValue getValue(ConfigSourceInterceptorContext context, String name) 方法拦截配置名称的分辨。使用 ConfigSourceInterceptorContext 继续执行拦截器链。通过返回 io.smallrye.config.ConfigValue 实例,可以短路执行该链。ConfigValue 对象保存着关于键名、值、配置源来历和序数的信息。

会在对某个配置值执行任何转换之前应用拦截器链。

也可以使用 io.smallrye.config.ConfigSourceInterceptorFactory 实现创建拦截器。每种实现都需要通过 META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory 文件中的 ServiceLoader 机制进行注册。

ConfigSourceInterceptorFactory 可能会初始化一个拦截器,赋予它对当前链的访问权限(以便可以使用它来配置拦截器和检索配置值),然后设置优先级。

Example

org.acme.config.LoggingConfigSourceInterceptor
package org.acme.config;

import static io.smallrye.config.SecretKeys.doLocked;

import jakarta.annotation.Priority;

import io.smallrye.config.ConfigSourceInterceptor;
import io.smallrye.config.ConfigLogging;

@Priority(Priorities.LIBRARY + 200)
public class LoggingConfigSourceInterceptor implements ConfigSourceInterceptor {
    private static final long serialVersionUID = 367246512037404779L;

    @Override
    public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) {
        ConfigValue configValue = doLocked(() -> context.proceed(name));
        if (configValue != null) {
            ConfigLogging.log.lookup(configValue.getName(), configValue.getLocation(), configValue.getValue());
        } else {
            ConfigLogging.log.notFound(name);
        }
        return configValue;
    }
}

并在以下文件中注册:

META-INF/services/io.smallrye.config.ConfigSourceInterceptor
org.acme.config.LoggingConfigSourceInterceptor

LoggingConfigSourceInterceptor 日志会在所提供的日志记录平台中查找配置名称。登录信息包括配置名称和值、配置源来历和(如果存在)位置。