Struct Util 是一个 Java 语言开发的结构化数据映射处理工具。Struct Util 主要解决两个方面的问题。第一个方面将*.xls, *.csv 等配置友好型数据源转换为业务侧友好型的 bean 结构,对配置数据和使用数据进行解耦,让开发和运营、策划三方实现共赢。第二方面解决了数据表热重载,数据有条件过滤,表结构跨表引用等等应用相关的问题。
点击阅读Struct Util 权威指南 - 配置文件的热重载
Struct Util 是一个 Java 语言开发的结构化数据映射处理工具。Struct Util 主要解决两个方面的问题。第一个方面将*.xls, *.csv 等配置友好型数据源转换为业务侧友好型的 bean 结构,对配置数据和使用数据进行解耦,让开发和运营、策划三方实现共赢。第二方面解决了数据表热重载,数据有条件过滤,表结构跨表引用等等应用相关的问题。
StructUtil是博主个人作品, 稍微有自吹自擂的嫌疑, 欢迎收藏。哈哈, 因为是个人作品,应该是足够”权威”了。嘻嘻~~
WatchService 注册监听文件变动
在 WatchService 注册需要监听的目录路径或文件路径,并指定仅关注 ENTRY_MODIFY 事件,路径相关的全部变动的事件通过 WatchKey#pollEvents()方法获取。核心实现业务代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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,当然,也可以执行多个其他自定义方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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 实现对文件系统的监听。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 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 实现文件的业务热重载功能。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 TinyZ Zzh (包含链接: https://tinyzzh.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。 如有任何疑问,请 与我联系 (tinyzzh815@gmail.com) 。
评论