It is required to monitor whether the configuration file has been modified. Due to the small function scale, Apollo, config and other components are not added, so it has to be implemented by itself
1. Self realization
The first idea is to implement it with timed tasks. The following is the author's implementation idea: FileModifyManager monitors and manages all files. To implement the monitoring interface FileListener and pass it to FileModifyManager, call the monitoring interface method doListener whenever the file changes
1.1 FileListener
@FunctionalInterface public interface FileListener { void doListener(); }
After reading the Hutool document, I know that this design is called hook function, and the author and Hutool have similar ideas
1.2 FileModifyManager
/** * @author Howl * @date 2022/01/15 */ public class FileModifyManager { // Store the monitored file and FileNodeRunnable node private static ConcurrentHashMap<File, FileNodeRunnable> data = new ConcurrentHashMap<>(16); // Thread pool performs scheduled listening tasks private static ScheduledExecutorService service = Executors.newScheduledThreadPool(20); // Singleton mode -- double check lock private volatile static FileModifyManager instance = null; private FileModifyManager() { } public static FileModifyManager getInstance() { if (instance == null) { synchronized (FileModifyManager.class) { if (instance == null) { instance = new FileModifyManager(); } } } return instance; } // Start listening. The default is once every 10 seconds public FileModifyManager startWatch(File file, FileListener fileListener) throws Exception { return startWatch(file, fileListener, 0, 1000 * 10, TimeUnit.MILLISECONDS); } public FileModifyManager startWatch(File file, FileListener fileListener, long delay, long period, TimeUnit timeUnit) throws Exception { FileNodeRunnable fileNodeRunnable = addFile(file, fileListener); ScheduledFuture<?> scheduledFuture = service.scheduleAtFixedRate(fileNodeRunnable, delay, period, timeUnit); fileNodeRunnable.setFuture(scheduledFuture); return instance; } // Stop listening public FileModifyManager stopWatch(File file) { return stopWatch(file, true); } public FileModifyManager stopWatch(File file, boolean mayInterruptIfRunning) { FileNodeRunnable fileNodeRunnable = data.get(file); fileNodeRunnable.getFuture().cancel(mayInterruptIfRunning); removeFile(file); return instance; } // Monitor or not public boolean isWatching(File file) { return containsFile(file); } // Listening list public Set listWatching() { return getFileList(); } // Management documents private FileNodeRunnable addFile(File file, FileListener fileListener) throws Exception { isFileExists(file); FileNodeRunnable fileNodeRunnable = new FileNodeRunnable(file, fileListener, file.lastModified()); data.put(file, fileNodeRunnable); return fileNodeRunnable; } private void removeFile(File file) { data.remove(file); } private boolean containsFile(File file) { return data.containsKey(file); } private Set getFileList() { return data.keySet(); } // Judge whether the file exists or not private void isFileExists(File file) throws Exception { if (!file.exists()) { throw new Exception("The file or path does not exist"); } } // File nodes and their scheduled tasks private class FileNodeRunnable implements Runnable { private File file; private long lastModifyTime; private FileListener listener; private ScheduledFuture future; FileNodeRunnable(File file, FileListener listener, long lastModifyTime) { this.file = file; this.listener = listener; this.lastModifyTime = lastModifyTime; } @Override public void run() { if (this.lastModifyTime != file.lastModified()) { System.out.println(file.toString() + " lastModifyTime is " + this.lastModifyTime); this.lastModifyTime = file.lastModified(); listener.doListener(); } } public ScheduledFuture getFuture() { return future; } public void setFuture(ScheduledFuture future) { this.future = future; } } }
Only startWatch, stopWatch and listWatching methods are exposed externally, and the input parameters are File and FileListener
Hutool also uses HashMap to store the corresponding relationship table, so the author's idea is quite clear
1.3 use cases
public class FileTest { public static void main(String[] args) throws Exception { File file1 = new File("C:\\Users\\Howl\\Desktop\\123.txt"); File file2 = new File("C:\\Users\\Howl\\Desktop\\1234.txt"); FileModifyManager manager = FileModifyManager.getInstance(); manager.startWatch(file1,() -> System.out.println("123.txt The file has changed")) .startWatch(file2,() -> System.out.println("1234.txr The file has changed")); } }
2. WatchService
WatchService uses the file system of the local operating system to realize the monitoring file directory (monitoring directory), which is available in jdk1 7 introduces a new mechanism under the NIO package, so the use method is also very similar to NIO
The disadvantage of the built-in watchService of JDK is that modifying a file will trigger two events, due to different conditions of the operating system:
- Modified the meta information and date of the file
- Copy on write effect, that is, rename the old file and copy the content to the new file
watchService can only monitor the contents of this directory and cannot detect the contents in subdirectories. If monitoring is required, it will traverse and add subdirectories
public class WatchServiceTest { public static void main(String[] args) throws IOException, InterruptedException { // Directory path, cannot enter file, otherwise an error will be reported Path path = Paths.get("C:\\Users\\Howl\\Desktop"); // Get listening service WatchService watchService = FileSystems.getDefault().newWatchService(); // Register only modification events (as well as creation and deletion) path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); // monitor while (true) { // Get the monitored event key WatchKey watchKey = watchService.poll(3 * 1000, TimeUnit.MILLISECONDS); // The return of poll may be null if (watchKey == null) { continue; } // Traverse these events for (WatchEvent<?> watchEvent : watchKey.pollEvents()) { if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { Path watchPath = (Path) watchEvent.context(); File watchFile = watchPath.toFile(); System.out.println(watchFile.toString() + "The file has been modified"); } } // watchKey recovery, used for next monitoring watchKey.reset(); } } }
3. Hutool (recommended)
Hutool is a tool set maintained by Chinese people. Using other people's wheels is always more efficient than building wheels repeatedly (but also understand the design idea of wheels). The bottom layer of hutool still uses WatchService, which solves that modifying files will trigger two events. The idea is that modifications within a certain millisecond range are regarded as the same modification. You can also monitor subdirectories. The idea is recursive traversal
3.1 adding dependencies
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version> </dependency>
Reference documents Hutool
3.2 example
public class HutoolTest { public static void main(String[] args) throws Exception { File file = new File("C:\\Users\\Howl\\Desktop\\123.txt"); // Listening file modification WatchMonitor watchMonitor = WatchMonitor.create(file, WatchMonitor.ENTRY_MODIFY); // Set hook function watchMonitor.setWatcher(new SimpleWatcher() { @Override public void onModify(WatchEvent<?> event, Path currentPath) { System.out.println(((Path) event.context()).toFile().toString() + "Modified"); } }); // Set the maximum depth of the listening directory. Changes with a directory level greater than the specified level will not be monitored. By default, only the current level directory will be monitored watchMonitor.setMaxDepth(1); // lsnrctl start watchMonitor.start(); } }
The idea is to inherit the Thread class, and then the run method keeps listening to the watchService event in a loop
4. commons-io
Commons IO is a tool set provided by Apache to implement I/O operations
4.1 adding dependencies
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency>
4.2 example
After a little look, the observer mode is used
public class CommonsTest { public static void main(String[] args) throws Exception { // It can only write directories String filePath = "C:\\Users\\Howl\\Desktop"; // Document observer FileAlterationObserver observer = new FileAlterationObserver(filePath); // Add listening observer.addListener(new FileAlterationListenerAdaptor() { @Override public void onFileChange(File file) { System.out.println(file.toString() + "The file has been modified"); } }); // monitor FileAlterationMonitor monitor = new FileAlterationMonitor(10); // Add observer monitor.addObserver(observer); // Start thread monitor.start(); } }