Extending Configuration Support
Custom ConfigSource
可以创建自定义的 ConfigSource
,如 MicroProfile Config 中所述。
有了自定义的 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
文件中。
Example
考虑一个简单的内存中 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
InMemoryConfigSource
将在 .env
源和 application.properties
源之间进行排序,这是由于 275
序数:
ConfigSource | Ordinal |
---|---|
System Properties |
400 |
Environment Variables from System |
300 |
来自 |
295 |
InMemoryConfigSource |
275 |
|
260 |
|
250 |
|
100 |
在此情况下,只有当配置引擎无法按照此顺序在 System Properties,Environment Variables from System 或 Environment Variables from .env file 中找到值时,才将使用来自 InMemoryConfigSource
的 my.prop
。
ConfigSource Init
当 Quarkus 应用程序启动时,可以初始化 ConfigSource
两次。一次针对 STATIC INIT,第二次针对 RUNTIME INIT:
STATIC INIT
Quarkus 在静态初始化期间启动其某些服务,而 Config
通常是最先创建的东西之一。在某些情况下,可能无法添加自定义 ConfigSource
。例如,如果 ConfigSource
要求其他服务,比如数据库访问,它在这个阶段将不可用,从而导致先有鸡还是先有蛋的问题。因此,任何自定义的 ConfigSource
都需要注释 @io.quarkus.runtime.configuration.StaticInitSafe
,以将源标记为在此阶段使用时是安全的。
Example
考虑:
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
可以在 STATIC INIT 期间提供。
定制的 ConfigSource
不会在 Quarkus STATIC INIT 期间自动添加。需要用 @io.quarkus.runtime.configuration.StaticInitSafe
注解标注。
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
考虑:
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
,一个 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));
}
}
定制转换器类必须是 |
定制配置类型自动地转换配置值:
@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 核心转换器都使用 |
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
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
日志会在所提供的日志记录平台中查找配置名称。登录信息包括配置名称和值、配置源来历和(如果存在)位置。