Extending Configuration Support

Custom ConfigSource

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

It’s possible to create a custom ConfigSource as specified in MicroProfile Config.

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

With a Custom ConfigSource it is possible to read additional configuration values and add them to the Config instance in a defined ordinal. This allows overriding values from other sources or falling back to other values.

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 文件中。

A custom ConfigSource requires an implementation of org.eclipse.microprofile.config.spi.ConfigSource or org.eclipse.microprofile.config.spi.ConfigSourceProvider. Each implementation requires registration via the ServiceLoader mechanism, either in META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource or META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider files.

Example

考虑一个简单的内存中 ConfigSource

Consider a simple in-memory 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();
    }
}

并在以下文件中注册:

And registration in:

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

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

The InMemoryConfigSource will be ordered between the .env source, and the application.properties source due to the 275 ordinal:

ConfigSource Ordinal

System Properties

400

Environment Variables from System

300

Environment Variables from .env file

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

In this case, my.prop from InMemoryConfigSource will only be used if the config engine is unable to find a value in System Properties, Environment Variables from System or Environment Variables from .env file in this order.

ConfigSource Init

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

When a Quarkus application starts, a ConfigSource can be initialized twice. One time for STATIC INIT and a second time for RUNTIME INIT:

STATIC INIT

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

Quarkus starts some of its services during static initialization, and Config is usually one of the first things that is created. In certain situations it may not be possible to add a custom ConfigSource. For instance, if the ConfigSource requires other services, like a database access, it will not be available at this stage, and cause a chicken-egg problem. For this reason, any custom ConfigSource requires the annotation @io.quarkus.runtime.configuration.StaticInitSafe to mark the source as safe to be used at this stage.

Example

考虑:

Consider:

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 {

}

并在以下文件中注册:

And registration in:

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

InMemoryConfigSource 可以在 STATIC INIT 期间提供。

The InMemoryConfigSource will be available during STATIC INIT.

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

A custom ConfigSource is not automatically added during Quarkus STATIC INIT. It requires to be marked with the @io.quarkus.runtime.configuration.StaticInitSafe annotation.

RUNTIME INIT

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

The RUNTIME INIT stage happens after STATIC INIT. In this stage a ConfigSource can be initialized again. There are no restrictions at this stage, and a custom source is added to the Config instance as expected.

ConfigSourceFactory

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

Another way to create a ConfigSource is via the SmallRye Config io.smallrye.config.ConfigSourceFactory API. The difference between the SmallRye Config factory and the standard way to create a ConfigSource as specified in MicroProfile Config, is the factory ability to provide a context with access to the available configuration.

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

Each implementation of io.smallrye.config.ConfigSourceFactory requires registration via the ServiceLoader mechanism in the META-INF/services/io.smallrye.config.ConfigSourceFactory file.

Example

考虑:

Consider:

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

并在以下文件中注册:

And registration in:

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 (如果找到)排序。

By implementing io.smallrye.config.ConfigSourceFactory, a list of ConfigSource may be provided via the Iterable<ConfigSource> getConfigSources(ConfigSourceContext context) method. The ConfigSourceFactory may also assign a priority by overriding the method OptionalInt getPriority(). The priority values is used to sort multiple io.smallrye.config.ConfigSourceFactory (if found).

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

io.smallrye.config.ConfigSourceFactory priority does not affect the ConfigSource ordinal. These are sorted independently.

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

When the Factory is initializing, the provided ConfigSourceContext may call the method ConfigValue getValue(String name). This method looks up configuration names in all ConfigSource`s that were already initialized by the `Config instance, including sources with lower ordinals than the ones defined in the ConfigSourceFactory. The ConfigSource list provided by a ConfigSourceFactory is not taken into consideration to configure other sources produced by a lower priority ConfigSourceFactory.

Custom Converter

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

It is possible to create a custom Converter type as specified by MicroProfile Config.

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

A custom Converter requires an implementation of org.eclipse.microprofile.config.spi.Converter<T>. Each implementation requires registration via the ServiceLoader mechanism in the META-INF/services/org.eclipse.microprofile.config.spi.Converter file. Consider:

package org.acme.config;

public class MicroProfileCustomValue {

    private final int number;

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

    public int getNumber() {
        return number;
    }
}

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

The corresponding converter will look similar to the one below.

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

The custom converter class must be public, must have a public constructor with no arguments, and must not be abstract.

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

The custom configuration type converts the configuration value automatically:

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

Converter priority

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

The jakarta.annotation.Priority annotation overrides the Converter priority and change converters precedence to fine tune the execution order. By default, if no @Priority is specified by the Converter, the converter is registered with a priority of 100. Consider:

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

Since it converts the same value type (MicroProfileCustomValue) and has a priority of 150, it will be used instead of a MicroProfileCustomValueConverter which has a default priority of 100.

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

All Quarkus core converters use the priority value of 200. To override any Quarkus specific converter, the priority value should be higher than 200.

Config Interceptors

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

SmallRye Config provides an interceptor chain that hooks into the configuration values resolution. This is useful to implement features like Profiles, Property Expressions, or just logging to find out where the config value was loaded from.

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

An interceptor requires an implementation of io.smallrye.config.ConfigSourceInterceptor. Each implementation requires registration via the ServiceLoader mechanism in the META-INF/services/io.smallrye.config.ConfigSourceInterceptor file.

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

The io.smallrye.config.ConfigSourceInterceptor is able to intercept the resolution of a configuration name with the method ConfigValue getValue(ConfigSourceInterceptorContext context, String name). The ConfigSourceInterceptorContext is used to proceed with the interceptor chain. The chain can be short-circuited by returning an instance of io.smallrye.config.ConfigValue. The ConfigValue objects hold information about the key name, value, config source origin and ordinal.

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

The interceptor chain is applied before any conversion is performed on the configuration value.

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

Interceptors may also be created with an implementation of io.smallrye.config.ConfigSourceInterceptorFactory. Each implementation requires registration via the ServiceLoader mechanism in the META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory file.

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

The ConfigSourceInterceptorFactory may initialize an interceptor with access to the current chain (so it can be used to configure the interceptor and retrieve configuration values) and set the priority.

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;
    }
}

并在以下文件中注册:

And registration in:

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

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

The LoggingConfigSourceInterceptor logs looks up configuration names in the provided logging platform. The log information includes config name and value, the config source origin and location if exists.