Struct Util 权威指南 - 配置文件的热重载

891 字
4 分钟
Struct Util 权威指南 - 配置文件的热重载

Struct Util 是一个 Java 语言开发的结构化数据映射处理工具。Struct Util 主要解决两个方面的问题。第一个方面将*.xls, *.csv 等配置友好型数据源转换为业务侧友好型的 bean 结构,对配置数据和使用数据进行解耦,让开发和运营、策划三方实现共赢。第二方面解决了数据表热重载,数据有条件过滤,表结构跨表引用等等应用相关的问题。

StructUtil是博主个人作品, 稍微有自吹自擂的嫌疑, 欢迎:star:收藏。哈哈, 因为是个人作品,应该是足够”权威”了。嘻嘻~~

WatchService 注册监听文件变动#

在 WatchService 注册需要监听的目录路径或文件路径,并指定仅关注 ENTRY_MODIFY 事件,路径相关的全部变动的事件通过 WatchKey#pollEvents()方法获取。核心实现业务代码如下:

public FileWatcherService register(String dir) throws IOException {
return this.register(Paths.get(Objects.requireNonNull(dir, "dir")));
}
public FileWatcherService register(Path dir) throws IOException {
Objects.requireNonNull(dir, "dir");
WatchKey key = dir.register(ws, StandardWatchEventKinds.ENTRY_MODIFY);
Path p = keys.putIfAbsent(key, dir);
if (null == p) {
LOGGER.info("Register file watcher service. path: {}", dir.toAbsolutePath());
}
return this;
}
public FileWatcherService registerAll(String path) throws IOException {
return this.registerAll(Paths.get(path));
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
public FileWatcherService registerAll(final Path start) throws IOException {
Objects.requireNonNull(start, "start");
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
return this;
}

注册文件变动钩子#

将路径和文件变动注册到 WatchService 之后,我们还可以注册自定义的 Hook,为了实现 StructStore 的 reload 热重载,我们再 Hook 中调用 reload,当然,也可以执行多个其他自定义方法。

public FileWatcherService registerHook(String fileName, Runnable hook) {
return this.registerHook(Paths.get(fileName), hook);
}
public FileWatcherService registerHook(Path path, Runnable hook) {
List<Runnable> list = this.hooksMap.computeIfAbsent(path, p -> Collections.synchronizedList(new ArrayList<>()));
list.add(hook);
LOGGER.info("Register file hook. path: {}", path.toAbsolutePath());
return this;
}
public FileWatcherService deregisterHook(String fileName) {
return this.deregisterHook(Paths.get(fileName));
}
public FileWatcherService deregisterHook(Path path) {
List<Runnable> l = this.hooksMap.remove(path);
if (null != l) {
LOGGER.info("Deregister file hook. path: {}", path.toAbsolutePath());
}
return this;
}

示例#

@Test
public void test() throws IOException {
FileWatcherService service = FileWatcherService.newBuilder().setWatchService(mockMs)
.setScheduleInitialDelay(10L)
.setScheduleTimeUnit(TimeUnit.DAYS)
.setScheduleDelay(999L)
.setExecutor(Executors.newScheduledThreadPool(1, r -> new Thread(r, "test")))
.build();
service.bootstrap();
service.register("./");
service.registerHook("./", () -> {
});
// service.deregisterHook("./");
service.run();
}

核心业务代码解析#

实现热重载的第一步就是要监听文件的变动,在文件变动时,调用 reload 方法对 Struct 数据进行重加载。Struct Util 中使用 JDK 内置的 WatchService 实现对文件系统的监听。

/**
* Schedule process the file watch events.
*/
private void process() {
try {
WatchKey key;
Path dir;
if ((key = ws.poll()) == null
|| (dir = keys.get(key)) == null) {
LOGGER.debug("watch key not registered. key:{}", key);
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path name = ev.context();
Path child = dir.resolve(name);
List<Runnable> l = hooksMap.get(child);
if (l != null) {
try {
l.forEach(Runnable::run);
} catch (Exception e) {
LOGGER.error("process data file failure. file:{}", child.toAbsolutePath(), e);
throw e;
}
}
}
boolean valid = key.reset();
if (!valid) {
this.keys.remove(key);
}
} catch (Throwable e) {
LOGGER.error("file watcher service throw an unknown exception.", e);
}
}

WatchService 的 poll 方法拉取新的文件变动事件,假如没有我们注册的的 WatchKey 则退出。否则使用 WatchKey#pollEvents()获取文件变动事件。

StandardWatchEventKind 主要由四个事件组成,分别为:

  • OVERFLOW:事件丢失或失去
  • ENTRY_CREATE:目录内实体创建或本目录重命名
  • ENTRY_MODIFY:目录内实体修改
  • ENTRY_DELETE:目录内实体删除或重命名

OVERFLOW 基本上忽略不处理,主要处理 ENTRY_CREATE 和 ENTRY_MODIFY 两种文件变动事件,ENTRY_DELETE 在游戏行业处理的比较少。

总结#

本文讲解了 StructUtil 使用 WatchService 实现文件和目录路径监听,并监听文件变动事件,注册文件变动 Hook,并使用 Hook 实现文件的业务热重载功能。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Struct Util 权威指南 - 配置文件的热重载
https://tinyzzh.github.io/posts/2023-05-01-struct_util_05_file_hot_reload/
作者
TinyZ Zzh
发布于
2023-05-01
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TinyZ Zzh
专注于高并发服务器、网络游戏相关(Java、PHP、Unity3D、Unreal Engine等)技术,热爱游戏事业, 正在努力实现自我价值当中。
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
211
分类
38
标签
200
总字数
337,853
运行时长
0
最后活动
0 天前

文章目录