Java——本地文件监听
1. 功能
在之前分析Nacos寻址机制时分享了文件寻址,其中实现了对本地文件的监听,当本地文件变更时做出对应的变化。在分布式潮流下,本地文件监听不常用,但当我们写像DevOps中间件,本地文件监听功能会变得非常重要。
2. 实现
-
FileChangeEvent: 文件变更事件,用于当监听到文件变化时传递给处理类的实体/** * 文件变更事件 * @author Tarzan写bug * @since 2022/09/13 */ public class FileChangeEvent { private String path; private Object context; public String getPath() { return path; } public void setPath(String path) { this.path = path; } public Object getContext() { return context; } public void setContext(Object context) { this.context = context; } public static FileChangeEventBuilder builder() { return new FileChangeEventBuilder(); } @Override public String toString() { return "FileChangeEvent{" + "path='" + path + '\'' + ", context='" + context + '\'' + '}'; } public static class FileChangeEventBuilder { private String path; private Object context; public FileChangeEventBuilder path(String path) { this.path = path; return this; } public FileChangeEventBuilder context(Object context) { this.context = context; return this; } public FileChangeEvent build() { FileChangeEvent event = new FileChangeEvent(); event.setPath(path); event.setContext(context); return event; } } } -
FileWatcher:文件监听器处理抽象类,监听到文件变化时执行对应的操作。可继承这个抽象类实现具体的逻辑/** * 文件监听处理抽象类 * @author Tarzan写bug * @date 2022/09/13 */ public abstract class FileWatcher { /** * 线程池,子类可重写该方法,自定义执行线程池 * @return */ public ExecutorService executor() { return null; } /** * 监听文件名 * @param context * @return */ abstract boolean watchFile(String context); /** * 文件变更操作 * @param event */ abstract void onChange(FileChangeEvent event); } -
FileWatchManager: 文件监听管理器,用于注册监听路径和初始化文件监听任务/** * 文件监听管理器 * @author Tarzan写bug * @since 2022/09/13 */ public class FileWatchManager { private static final Logger LOGGER = LoggerFactory.getLogger(FileWatchManager.class); /** * 最大文件监听数量 */ private static final int MAX_WATCH_FILE_COUNT = 16; /** * 文件监听任务集合 */ private static Map<String, FileWatchJob> MANAGER = new HashMap<>(MAX_WATCH_FILE_COUNT); /** * 用于关闭线程池时的CAS操作 */ private static final AtomicBoolean CLOSED = new AtomicBoolean(false); /** * 初始监听文件数量 */ private static int WATCH_FILE_CNT = 0; private static final FileSystem FILE_SYSTEM = FileSystems.getDefault(); static { // 应用关闭回调 ThreadUtil.addShutdownHook(new Runnable() { @Override public void run() { shutdown(); } }); } /** * 注册文件监听处理器 * @param path * @param watcher */ public static void registerWatcher(String path, FileWatcher watcher) { checkState(); if (WATCH_FILE_CNT == MAX_WATCH_FILE_COUNT) { return; } FileWatchJob fileWatchJob = MANAGER.get(path); if (fileWatchJob == null) { // 初始化文件监听任务 fileWatchJob = new FileWatchJob(path); fileWatchJob.start(); MANAGER.put(path, fileWatchJob); WATCH_FILE_CNT++; } // 监听任务加入文件监听处理器 fileWatchJob.addSubscribe(watcher); } /** * 检查状态 */ private static void checkState() { if (CLOSED.get()) { throw new IllegalStateException("FileWatchManager already shutdown"); } } /** * 优雅关闭 */ public static void shutdown() { if (!CLOSED.compareAndSet(false, true)) { return; } for (FileWatchJob value : MANAGER.values()) { value.shutdown(); } MANAGER.clear(); WATCH_FILE_CNT = 0; } /** * 文件监听任务 */ public static class FileWatchJob extends Thread { /** * 文件路径 */ private String path; /** * 监听处理线程池 */ private ExecutorService callBackExecutor; /** * 监听处理器集合 */ private List<FileWatcher> fileWatchers; private WatchService watchService; /** * 用于优雅关闭线程时不在循环监听标识,所以要用volatile修饰,及时获取最新值 */ private volatile boolean watch = true; public FileWatchJob(String path) { setName(path); this.path = path; // 监听路径 Path watchPath = Paths.get(path); // 初始化线程池 callBackExecutor = ExecutorFactory .newSingleExecutorService(new NameThreadFactory("com.mountain.file.FileWatchManager-" + path)); try { // 注册文件监听事件 watchService = FILE_SYSTEM.newWatchService(); watchPath.register(watchService, StandardWatchEventKinds.OVERFLOW, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE); } catch (Exception e) { throw new RuntimeException("监听文件异常:" + e.getMessage()); } } /** * 添加文件监听处理器 * @param watcher */ public void addSubscribe(FileWatcher watcher) { fileWatchers.add(watcher); } /** * 关闭线程池 */ public void shutdown() { watch = false; ThreadUtil.shutdownThreadPool(callBackExecutor); } @Override public void run() { while (watch) { try { // 获取文件变更事件 WatchKey watchKey = watchService.take(); List<WatchEvent<?>> watchEvents = watchKey.pollEvents(); watchKey.reset(); if (watchEvents == null || watchEvents.isEmpty()) { continue; } callBackExecutor.execute(new Runnable() { @Override public void run() { for (WatchEvent<?> watchEvent : watchEvents) { WatchEvent.Kind<?> kind = watchEvent.kind(); if (StandardWatchEventKinds.OVERFLOW.equals(kind)) { overflowEventProcess(); } else { eventProcess(watchEvent.context()); } } } }); } catch (InterruptedException e) { Thread.interrupted(); } catch (Throwable ex) { LOGGER.error("FileWatchJob run exception: {}", ex.getMessage()); } } } private void eventProcess(Object context) { FileChangeEvent event = FileChangeEvent.builder().path(path).context(context).build(); String name = String.valueOf(context); // 循环处理器 for (FileWatcher watcher : fileWatchers) { // 是否是监听的文件名 if (watcher.watchFile(name)) { Runnable job = new Runnable() { @Override public void run() { // 调用处理器onChange方法 watcher.onChange(event); } }; ExecutorService executor = watcher.executor(); if (executor == null) { try { job.run(); } catch (Exception e) { LOGGER.error("FileWatcher process exception: {}", e.getMessage()); } } else { executor.execute(job); } } } } private void overflowEventProcess() { File dir = Paths.get(path).toFile(); for (File file : Objects.requireNonNull(dir.listFiles())) { if (file.isDirectory()) { continue; } eventProcess(file.getName()); } } } } -
使用
-
实现
FileWatcherprivate FileWatcher watcher = new FileWatcher() { @Override boolean watchFile(String context) { return context.equals("gitops.conf"); } @Override void onChange(FileChangeEvent event) { LOGGER.info("receive file change"); } }; -
注册到文件监听管理器
FileWatchManager.registerWatcher("/home/", watcher);
-
3. 总结
上述实现基于INotify机制实现,通过监听回调可以思考下Nacos客户端对配置的监听是否类似实现呢?上述代码收录在https://gitee.com/ouwenrts/tuyere.git
谢谢阅读,就分享到这,未完待续...
欢迎同频共振的那一部分人
作者公众号:Tarzan写bug