这篇文章将介绍如何使用Guava EventBus将更改发布到Java 7 WatchService检测到的目录或子目录中。 Guava EventBus是向应用程序添加发布/订阅通信的好方法。 Java 7 java.nio.file软件包中新增的WatchService用于监视目录中的更改。 由于EventBus和WatchService已在以前的文章中介绍过,因此我们在这里不会深入介绍这些主题。 有关更多信息,鼓励读者查看EventBus和WatchService帖子。 [注意:为清楚起见,帖子于2012年2月28日更新。]
为什么使用EventBus
将EventBus与WatchService一起使用的主要原因有两个。
- 我们不希望轮询事件,而是希望接收异步通知。
- 处理事件后,需要调用WatchKey.reset方法以使所有新更改都可以排队。 尽管WatchKey对象是线程安全的,但重要的是仅在所有线程完成处理事件之后才调用reset方法,这会导致一些协调麻烦。 使用单个线程处理事件,调用reset方法,然后通过EventBus发布更改,消除了此问题。
我们实现这一目标的计划很简单,将涉及以下步骤:
- 实例化WatchService的实例。
- 从给定的Path对象开始递归注册每个目录。
- 将事件从WatchService队列中移出,然后处理并发布这些事件。
- 启动一个单独的线程以使事件脱离队列并发布。
下面的代码示例是DirectoryEventWatcherImpl类中更相关的重点,它将完成所有这些工作。
在WatchService中注册目录
在添加或删除子目录时将生成事件,而在监视目录的子目录内进行的任何更改均不会。 我们将通过递归遍历所有子目录(通过Files.walkFileTree方法)并使用WatchService对象(在此示例中先前定义)注册每个子目录来对此进行补偿:
private void registerDirectories() throws IOException {Files.walkFileTree(startPath, new WatchServiceRegisteringVisitor());
}private class WatchServiceRegisteringVisitor extends SimpleFileVisitor<Path>{@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {dir.register(watchService,ENTRY_CREATE,ENTRY_DELETE,ENTRY_MODIFY);return FileVisitResult.CONTINUE;}
}
在第2行,Files.walkFileTree方法使用在第5行定义的WatchServiceRegisteringVisitor类向WatchService注册每个目录。 所注册的事件是文件/目录的创建,文件/目录的删除或文件的更新。
发布事件
下一步是创建一个FutureTask,它将执行检查队列和发布事件的工作。
private void createWatchTask() {watchTask = new FutureTask<>(new Callable<Integer>() {private int totalEventCount;@Overridepublic Integer call() throws Exception {while (keepWatching) {WatchKey watchKey = watchService.poll(10, TimeUnit.SECONDS);if (watchKey != null) {List<WatchEvent<?>> events = watchKey.pollEvents();Path watched = (Path) watchKey.watchable();PathEvents pathEvents = new PathEvents(watchKey.isValid(), watched);for (WatchEvent event : events) {pathEvents.add(new PathEvent((Path) event.context(), event.kind()));totalEventCount++;}watchKey.reset();eventBus.post(pathEvents);}}return totalEventCount;}});}private void startWatching() {new Thread(watchTask).start();
}
在第7行,我们每10秒检查一次WatchService是否有排队事件。 当返回有效的WatchKey时,第一步是检索事件(第9行),然后获取发生事件的目录(第10行)。 在第11行,将创建一个PathEvents对象,该对象将一个布尔值和受监视的目录用作构造函数参数。 第12至15行使用目标Path和事件类型作为创建PathEvent对象的参数遍历第9行检索到的事件。 在第16行调用WatchKey.reset方法,将WatchKey状态设置回ready,使其有资格接收新事件并将其放回到队列中。 最后,在第17行,EventBus将PathEvents对象发布给所有订阅者。 重要的是在这里注意PathEvents和PathEvent类是不可变的。 从Callable返回的totalEventCount永远不会在API中公开,而是用于测试目的。 第25行的startWatching方法启动线程以运行上面定义的监视/发布任务。
结论
通过将WatchService与Guava EventBus配对,我们可以在单个线程中管理WatchKey并处理事件,并以异步方式通知任意数量的订阅者该事件。 希望读者发现此示例有用。 一如既往地欢迎提出意见和建议。
资源资源
- 这篇文章的源代码和单元测试
- EventBus API
- WatchService API
- WatchService上的上一篇文章 。
- EventBus上的上一篇文章
参考: 事件编程示例:来自JCG合作伙伴 Bill Bejeck的Google Guava EventBus和Java 7 WatchService,来自“ 随机思考编码”博客。
翻译自: https://www.javacodegeeks.com/2012/12/google-guava-eventbus-and-java-7-watchservice-for-event-programming.html