WatchMonitor

Origins

There are many times when we need to listen for changes in a file or directory, including the creation, modification, and deletion of files, as well as the creation, modification, and deletion of files in a directory. Before JDK7, we could only traverse the directory using polling or check for file modification events at regular intervals, which is inefficient and has poor performance. Therefore, WatchService was introduced in JDK7. However, considering that its API is not user-friendly, Hutool has simplified the encapsulation of it to make listening easier and provide better functionality, including:

  • Supports multi-level directory listening (WatchService only supports one level), with the option to customize the depth of the listening directory
  • Supports delayed merge triggering (file changes may trigger multiple modify events, support merging multiple modify events into one modify event within a certain time range)
  • Has简洁易懂的API方法,一个方法即可完成监听,无需理解复杂的监听注册机制。
  • Has multiple observer implementations that can respond to the same event with multiple Watchers (through WatcherChain)

WatchMonitor

In Hutool, WatchMonitor mainly focuses on encapsulating WatchService in JDK7 for file and directory changes, acting as a hook for creating, updating, and deleting files and directories, and defining corresponding logic in the Watcher to respond to these file changes.

Internal Application

In the hutool-setting module, WatchMonitor is used to monitor configuration file changes and then automatically load them into memory. The use of WatchMonitor avoids polling and responds to file changes with event-based responses.

Usage

WatchMonitor provides the following events:

  • ENTRY_MODIFY for file modification events
  • ENTRY_CREATE for file or directory creation events
  • ENTRY_DELETE for file or directory deletion events
  • OVERFLOW for lost events

These events correspond to the events in StandardWatchEventKinds.

Below, we introduce the usage of WatchMonitor:

Listening for Specific Events

File file = FileUtil.file("example.properties");
// Here we only listen for file or directory modification events
WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY);
watchMonitor.setWatcher(new Watcher(){
	@Override
	public void onCreate(WatchEvent<?> event, Path currentPath) {
		Object obj = event.context();
		Console.log("创建:{}-> {}", currentPath, obj);
	}

	@Override
	public void onModify(WatchEvent<?> event, Path currentPath) {
		Object obj = event.context();
		Console.log("修改:{}-> {}", currentPath, obj);
	}

	@Override
	public void onDelete(WatchEvent<?> event, Path currentPath) {
		Object obj = event.context();
		Console.log("删除:{}-> {}", currentPath, obj);
	}

	@Override
	public void onOverflow(WatchEvent<?> event, Path currentPath) {
		Object obj = event.context();
		Console.log("Overflow:{}-> {}", currentPath, obj);
	}
});

// Set the maximum depth of the listening directory. Changes at directory levels deeper than the specified level will not be listened to. By default, only the current level directory is listened to.
watchMonitor.setMaxDepth(3);
// Start listening
watchMonitor.start();

Listening to All Events

Actually, we don’t have to implement all the interface methods of Watcher. Hutool also provides a SimpleWatcher class, which only needs to override the corresponding methods.

Similarly, if we want to listen to all events, we can do it like this:

WatchMonitor.createAll(file, new SimpleWatcher() {
 @Override
 public void onModify(WatchEvent<?> event, Path currentPath) {
 Console.log("EVENT modify");
 }
}).start();

The createAll method will create a WatchMonitor that listens to all events, and define a Watcher in the second parameter to handle these changes.

Delayed Processing of Listening Events

When monitoring a directory or file, if the file is modified, JDK will trigger the modify method multiple times. To solve this problem, we define a DelayWatcher, which maintains a Set to merge the modify events of the same file within a short time for processing and triggering, thereby avoiding the above problem.

WatchMonitor monitor = WatchMonitor.createAll("d:/", new DelayWatcher(watcher, 500));
monitor.start();