Java——本地文件监听

Java——本地文件监听

1. 功能

在之前分析Nacos寻址机制时分享了文件寻址,其中实现了对本地文件的监听,当本地文件变更时做出对应的变化。在分布式潮流下,本地文件监听不常用,但当我们写像DevOps中间件,本地文件监听功能会变得非常重要。

2. 实现

Java——本地文件监听_第1张图片

  • 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());
                }
            }
        }
    }
    
  • 使用

    1. 实现FileWatcher

      private FileWatcher watcher = new FileWatcher() {
          @Override
          boolean watchFile(String context) {
              return context.equals("gitops.conf");
          }
      
          @Override
          void onChange(FileChangeEvent event) {
              LOGGER.info("receive file change");
          }
      };
      
    2. 注册到文件监听管理器

      FileWatchManager.registerWatcher("/home/", watcher);
      

3. 总结

上述实现基于INotify机制实现,通过监听回调可以思考下Nacos客户端对配置的监听是否类似实现呢?上述代码收录在https://gitee.com/ouwenrts/tuyere.git



谢谢阅读,就分享到这,未完待续…

欢迎同频共振的那一部分人

作者公众号:Tarzan写bug

你可能感兴趣的:(java,devops)