扩展配置支持
1. 自定义 ConfigSource
可以根据 MicroProfile Config 的规定创建自定义 ConfigSource
。
使用自定义 ConfigSource
,可以读取其他配置值,并以定义的顺序将它们添加到 Config
实例中。这允许覆盖其他源的值或回退到其他值。

自定义 ConfigSource
需要实现 org.eclipse.microprofile.config.spi.ConfigSource
或 org.eclipse.microprofile.config.spi.ConfigSourceProvider
。每个实现都需要通过 ServiceLoader 机制进行注册,无论是通过 META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
还是 META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider
文件。
1.1. 示例
考虑一个简单的内存中 ConfigSource
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();
}
}
以及在以下位置的注册
org.acme.config.InMemoryConfigSource
由于 275
的顺序,InMemoryConfigSource
将排在 .env
源和 application.properties
源之间
ConfigSource | 顺序 |
---|---|
系统属性 |
400 |
系统环境变量 |
300 |
来自 |
295 |
内存ConfigSource |
275 |
来自 |
260 |
来自应用程序的 |
250 |
来自应用程序的 |
100 |
在这种情况下,只有当配置引擎无法按此顺序在 系统属性、系统环境变量 或 来自 .env 文件的环境变量 中找到值时,才会使用 InMemoryConfigSource
中的 my.prop
。
1.2. ConfigSource 初始化
Quarkus 应用程序启动时,ConfigSource
可以初始化两次。一次用于静态初始化,第二次用于运行时初始化。
1.2.1. 静态初始化
Quarkus 在静态初始化期间启动一些服务,而 Config
通常是创建的第一批事物之一。在某些情况下,可能无法添加自定义 ConfigSource
。例如,如果 ConfigSource
需要其他服务,例如数据库访问,它在这个阶段将不可用,并导致鸡生蛋蛋生鸡的问题。因此,任何自定义 ConfigSource
都需要 @io.quarkus.runtime.configuration.StaticInitSafe
注释来标记该源在此阶段是安全的。
1.2.1.1. 示例
考虑
package org.acme.config;
import org.eclipse.microprofile.config.spi.ConfigSource;
import io.quarkus.runtime.annotations.StaticInitSafe;
@StaticInitSafe
public class InMemoryConfigSource implements ConfigSource {
}
以及在以下位置的注册
org.acme.config.InMemoryConfigSource
InMemoryConfigSource
将在静态初始化期间可用。
自定义 ConfigSource 不会在 Quarkus静态初始化期间自动添加。它需要用 @io.quarkus.runtime.configuration.StaticInitSafe 注释进行标记。 |
2. ConfigSourceFactory
创建 ConfigSource
的另一种方法是通过 SmallRye Config 的 io.smallrye.config.ConfigSourceFactory
API。 SmallRye Config 工厂与根据 MicroProfile Config 规范创建 ConfigSource
的标准方法之间的区别在于,工厂能够提供一个具有对可用配置的访问权限的上下文。
io.smallrye.config.ConfigSourceFactory
的每个实现都需要通过 ServiceLoader 机制在 META-INF/services/io.smallrye.config.ConfigSourceFactory
文件中进行注册。
2.1. 示例
考虑
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);
}
}
以及在以下位置的注册
org.acme.config.URLConfigSourceFactory
通过实现 io.smallrye.config.ConfigSourceFactory
,可以通过 Iterable<ConfigSource> getConfigSources(ConfigSourceContext context)
方法提供一个 ConfigSource
列表。ConfigSourceFactory
还可以通过覆盖 OptionalInt getPriority()
方法来分配优先级。优先级值用于对多个 io.smallrye.config.ConfigSourceFactory
(如果找到) 进行排序。
io.smallrye.config.ConfigSourceFactory 的优先级不会影响 ConfigSource 的顺序。它们是独立排序的。 |
当工厂初始化时,提供的 ConfigSourceContext
可以调用 ConfigValue getValue(String name)
方法。此方法会在 Config
实例已初始化的所有 ConfigSource
中查找配置名称,包括顺序低于 ConfigSourceFactory
中定义的顺序的源。由较低优先级的 ConfigSourceFactory
生成的其他源的配置不考虑 ConfigSourceFactory
提供的 ConfigSource
列表。
3. 自定义 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;
3.1. 转换器优先级
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 。 |
4. 配置拦截器
SmallRye Config 提供了一个拦截器链,该链钩入配置值的解析。这对于实现配置文件、属性表达式等功能非常有用,或者仅仅是为了日志记录以找出配置值是从哪里加载的。
拦截器需要实现 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
来创建。每个实现都需要通过 ServiceLoader 机制在 META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory
文件中进行注册。
ConfigSourceInterceptorFactory
可以在访问当前链的情况下初始化拦截器(这样就可以用于配置拦截器并检索配置值),并设置优先级。
4.1. 示例
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;
}
}
以及在以下位置的注册
org.acme.config.LoggingConfigSourceInterceptor
LoggingConfigSourceInterceptor
在提供的日志平台中记录查找配置名称。日志信息包括配置名称和值,以及配置源的来源和位置(如果存在)。