vertx-config
介绍
vertx-config
主要负责vert.x
的配置文件导入,可以导入json
、properties
等等格式的配置文件,配置文件的来源可以是本地文件
、http获取
、配置中心
等等。
使其可用
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
</dependency>
SPI机制
如果你看过vert.x
的源码,就会很清楚什么是SPI
了,vert.x
的模块大多是基于SPI实现的。这里简单介绍下SPI。
SPI
全称为 (Service Provider Interface
)。
SPI
是JDK内置的一种服务提供发现机制。
SPI
是一种动态替换发现的机制。
比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver
接口,其他不同厂商可以针对同一接口做出不同的实现,mysql
和postgresql
都有不同的实现提供给用户,而Java的SPI
机制可以为某个接口寻找服务实现。
vertx-config
的SPI
1. ConfigStore
ConfigStore
是一个SPI的接口,用于指定配置文件来源,而具体实现都在io.vertx.config.impl.spi
中,该package
中可以看到很多实现类,如下
DirectoryConfigStore
从本地目录加载配置文件;EventBusConfigStore
从EventBus
加载配置文件;FileConfigStore
加载指定的配置文件;HttpConfigStore
通过http
请求获取配置文件;JsonConfigStore
加载json
格式的配置文件;
public interface ConfigStore {
/**
* Closes the configuration store.
*
* @param completionHandler handler called when the cleanup has been completed
*/
default void close(Handler<Void> completionHandler) {
completionHandler.handle(null);
}
/**
* Retrieves the configuration store in this store.
*
* @param completionHandler the handler to pass the configuration
*/
void get(Handler<AsyncResult<Buffer>> completionHandler);
}
vertx-config
提供了常用的一些配置文件来源,在此基础上,我们也可以自己去扩展,比如从git
获取配置文件,很简单的三个类就可以轻松实现,详细可以参考 github.com/vert-x3/ver…
2. ConfigProcessor
ConfigProcessor
也是一个SPI的接口,用于指定配置文件解析的方式,而具体实现都在io.vertx.config.impl.spi
中,该package
中可以看到很多实现类,如下
JsonProcessor
以json
形式解析配置文件;PropertiesConfigProcessor
以properties
形式解析配置文件;RawProcessor
以raw
形式解析配置文件;
public class JsonProcessor implements ConfigProcessor {
@Override
public void process(Vertx vertx, JsonObject configuration, Buffer input, Handler<AsyncResult<JsonObject>> handler) {
try {
JsonObject json = input.toJsonObject();
if (json == null) {
json = new JsonObject();
}
handler.handle(Future.succeededFuture(json));
} catch (Exception e) {
handler.handle(Future.failedFuture(e));
}
}
@Override
public String name() {
return "json";
}
}
如上是JsonProcessor
的实现,看起来非常简单,将获取的文件内容Buffer
直接转化成JsonObject
并返回。
vert.x
提倡异步,对外提供的服务也都是异步的,返回一个AsyncResult
等待后续处理。
3. ConfigStoreFactory
ConfigStoreFactory 也是一个SPI的接口,是
ConfigStore
的工厂,而具体实现都在io.vertx.config.impl.spi
中,该package
中可以看到很多实现类,比如DirectoryConfigStoreFactory
是用于创建DirectoryConfigStore
实现类的工厂。
public class DirectoryConfigStoreFactory implements ConfigStoreFactory {
@Override
public String name() {
return "directory";
}
@Override
public ConfigStore create(Vertx vertx, JsonObject configuration) {
return new DirectoryConfigStore(vertx, configuration);
}
}
具体实现也非常简单,首先做了一个name标示是directory
,其次实现了创建ConfigStore
的方法。
4.总结
vertx-config
对扩展是开放的,同时实现了部分常用的场景。我们在自定义业务时,只需要这三个SPI,即可将我们的业务嵌入其中,很简单,很快乐。
怎么运行起来的?
1. ConfigRetriever 加载
retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig(ar -> {
//...
});
ConfigRetriever
是配置文件加载的入口,我们在Verticle
中构建ConfigRetriever
即可轻松实现配置文件的加载。
2. 定时刷新
static ConfigRetriever create(Vertx vertx, ConfigRetrieverOptions options) {
ConfigRetrieverImpl retriever = new ConfigRetrieverImpl(vertx, options);
retriever.initializePeriodicScan();
return retriever;
}
ConfigRetrieverImpl
是ConfigRetriever
的实现类,创建实现类后,同时通过initializePeriodicScan()
初始化了定时刷新。
定时刷新的默认配置是5秒。
private static final long SCAN_PERIOD_DEFAULT = 5000L;
而检查更新的方法也非常简单,将新的配置文件覆盖到current
中。(current = ar.result();
)
private void scan() {
if (beforeScan != null) {
beforeScan.handle(null);
}
compute(ar -> {
if (ar.failed()) {
streamOfConfiguration.fail(ar.cause());
LOGGER.error("Error while scanning configuration", ar.cause());
} else {
synchronized (ConfigRetrieverImpl.this) {
// Check for changes
if (!current.equals(ar.result())) {
JsonObject prev = current;
current = ar.result();
listeners.forEach(l -> l.handle(new ConfigChange(prev, current)));
try {
streamOfConfiguration.handle(current);
} catch (Throwable e) {
// Report the error on the context exception handler.
if (vertx.exceptionHandler() != null) {
vertx.exceptionHandler().handle(e);
} else {
throw e;
}
}
}
}
}
});
}
在这个配置刷新功能中,还提供了监听配置前后变化的功能,如下
retriever.listen(change -> {
JsonObject json = change.getNewConfiguration();
vertx.eventBus().publish("new-configuration", json);
});
3. ConfigRetrieverImpl 的创建
第一步,先将所有的ConfigStoreFactory
工厂加载进来,按照键值组成一个nameToImplMap
,供后面使用。
ServiceLoader<ConfigStoreFactory> storeImpl =
ServiceLoader.load(ConfigStoreFactory.class,
ConfigStoreFactory.class.getClassLoader());
Map<String, ConfigStoreFactory> nameToImplMap = new HashMap<>();
storeImpl.iterator().forEachRemaining(factory -> nameToImplMap.put(factory.name(), factory));
if (nameToImplMap.isEmpty()) {
throw new IllegalStateException("No configuration store implementations found on the classpath");
}
第二步,整理ConfigStoreOptions
中配置信息。
List<ConfigStoreOptions> stores = options.getStores();
if (options.isIncludeDefaultStores()) {
stores = new ArrayList<>();
stores.add(
new ConfigStoreOptions().setType("json")
.setConfig(vertx.getOrCreateContext().config()));
stores.add(new ConfigStoreOptions().setType("sys"));
stores.add(new ConfigStoreOptions().setType("env"));
// Insert the default config if configured.
String defaultConfigPath = getDefaultConfigPath();
if (defaultConfigPath != null && ! defaultConfigPath.trim().isEmpty()) {
String format = extractFormatFromFileExtension(defaultConfigPath);
LOGGER.info("Config file path: " + defaultConfigPath + ", format:" + format);
stores.add(new ConfigStoreOptions()
.setType("file").setFormat(format)
.setOptional(true)
.setConfig(new JsonObject().put("path", defaultConfigPath)));
}
stores.addAll(options.getStores());
}
vert.x
设置了默认的ConfigStoreOptions
,主要用于加载环境配置信息和vert.x
的默认加载的配置文件。
第三步,根据配置信息,加载ConfigStore
和ConfigProcessor
providers = new ArrayList<>();
for (ConfigStoreOptions option : stores) {
String type = option.getType();
if (type == null) {
throw new IllegalArgumentException(
"the `type` entry is mandatory in a configuration store configuration");
}
ConfigStoreFactory factory = nameToImplMap.get(type);
if (factory == null) {
throw new IllegalArgumentException("unknown configuration store implementation: " +
type + " (known implementations are: " + nameToImplMap.keySet() + ")");
}
JsonObject config = option.getConfig();
if (config == null) {
config = new JsonObject();
}
ConfigStore store = factory.create(vertx, config);
String format = option.getFormat() != null ? option.getFormat() : "json";
ConfigProcessor processor = Processors.get(format);
if (processor == null) {
throw new IllegalArgumentException("unknown configuration format: " + format + " (supported formats are: " +
Processors.getSupportedFormats());
}
providers.add(new ConfigurationProvider(store, processor, option.getConfig(), option.isOptional()));
}
通过源码可以看到ConfigStoreOptions
中需要配置的信息
- type是必填的;
- type必须要有对应的
ConfigStoreFactory
实现; - config是选填的,如果为null,则会给初始化
new JsonObject()
; - format是选填的,如果为null,则会给初始化
json
; - format必须要有对应的
ConfigProcessor
实现;
一般项目配置
一般配置ConfigRetrieverOptions
时,看业务需求,可以选择禁用配置刷新
、缓存
等等。
下面是我项目中的一个简单的配置,仅供参考。
private ConfigRetrieverOptions initOptions() {
// 使用默认ConfigStore
ConfigRetrieverOptions options = new ConfigRetrieverOptions().setIncludeDefaultStores(true);
// 禁用配置刷新
options.setScanPeriod(-1);
// 导入需要加载的Verticle
options.addStore(new ConfigStoreOptions()
.setType("file")
.setFormat("json")
.setOptional(true)
.setConfig(new JsonObject().put("path", "verticles.json")));
// 导入SQL脚本
options.addStore(new ConfigStoreOptions()
.setType("file")
.setFormat("json")
.setOptional(true)
.setConfig(new JsonObject().put("path", "sql.json")));
// 导入阿里云基础配置
options.addStore(new ConfigStoreOptions()
.setType("file")
.setFormat("json")
.setOptional(true)
.setConfig(new JsonObject().put("path", "conf/aliyun.json")));
// 禁用缓存
options.getStores().forEach(store -> {
store.getConfig().put("cache", "false");
});
return options;
}
总结
vertx-config
这个模块结构简单明了,SPI机制简明易懂,算是日常学习的好材料。其实在读vert.x
的过程中,深深得体会到了vert.x
的简洁。
vert.x
给我的印象就是高效而不晦涩
。