Nội dung
Giới thiệu
Khi đang chỉnh sửa một tệp, sử dụng một IDE hay một trình soạn thảo khác, và một hộp thoại xuất hiện để thông báo cho bạn rằng một trong các tệp đang mở đã thay đổi trên hệ thống tệp và cần được tải lại? Hay một trình quản lý file có thể tức thời cập nhật, hiển thị danh sách file khi file được tạo hoặc xóa hoặc sửa đổi. Có bao giờ bạn hỏi: tại sao họ có thể làm được như vậy? họ làm bằng cách nào?
Để thực hiện chức năng này, một chương trình phải có khả năng phát hiện những gì đang xảy ra với thư mục có liên quan trên hệ thống file. Khi có thay đổi, nó sẽ được gọi để xử lý và hiển thị tương ứng.
Java 7 đã thêm một tính năng mới vào gói java.nio.file là Watch Service API. API này cho phép bạn đăng ký một thư mục (hoặc các thư mục) với Watch Service API. Khi đăng ký, bạn cần cho Service biết loại sự kiện nào bạn quan tâm: tạo, xóa hoặc sửa đổi file. Khi Service phát hiện một sự kiện đã đăng ký, nó sẽ được chuyển tiếp đến tiến trình đã đăng ký. Quá trình đăng ký có một thread (hoặc thread pool) dành riêng để giám sát cho bất kỳ sự kiện nào mà nó đã đăng ký. Khi một sự kiện xuất hiện, nó được xử lý khi cần thiết.
Trong phần tiếp theo của bài viết này, chúng ta sẽ cùng tìm hiểu cách sử dụng WatchService trong Java.
Các phương thức được cung cấp bởi Watch Service
- close() : đóng Watch Service.
- poll() : truy xuất và xóa key tiếp theo hoặc null nếu không tồn tại bất kỳ key nào.
- poll(long timeout, TimeUnit unit) : truy xuất và xóa key tiếp theo hoặc chờ đợi nếu cần thiết cho đến thời gian chờ đã chỉ định nếu chưa có.
- take() : truy xuất và xóa key tiếp theo hoặc đợi nếu không có bất kỳ key nào.
Các loại sự kiện được đăng ký với Watch Service
Các sự kiện được định nghĩa trong class StandardWatchEventKinds:
ENTRY_CREATE
: một directory đã được tạo.ENTRY_DELETE
: một directory đã được xóa.ENTRY_MODIFY
: một directory đã được sửa.OVERFLOW
: cho biết rằng các sự kiện có thể đã bị mất hoặc bị loại bỏ. Bạn không cần phải đăng ký cho sự kiện OVERFLOW để nhận nó.
Các bước để sử dụng Watch Service
Dưới đây là các bước cơ bản cần thiết để sử dụng WatchService:
- Tạo một WatchService “watcher” cho hệ thống tập tin.
- Đối với mỗi thư mục mà chúng ta muốn theo dõi, hãy đăng ký nó với watcher. Khi đăng ký một thư mục, chúng ta cần chỉ định loại sự kiện muốn thông báo. Sự kiện có thể là tạo, xóa hoặc sửa đổi. Chúng ta nhận được một instance WatchKey cho mỗi thư mục đã đăng ký.
- Thực hiện một vòng lặp vô hạn để chờ các sự kiện đến. Khi một sự kiện xảy ra, key được báo hiệu và được đặt vào hàng đợi (queue) của watcher.
- Truy xuất key (WatchKey) từ hàng đợi của watcher. Chúng ta có thể lấy tên file từ key.
- Truy xuất mỗi sự kiện đang chờ xử lý cho key (có thể có nhiều sự kiện) và xử lý khi cần.
- Đặt lại key và tiếp tục chờ sự kiện. Điều quan trọng là phải đặt lại key sau khi các sự kiện đã được xử lý. Nếu không, key sẽ không nhận được thêm sự kiện. Nếu phương thức reset() trả về false, thư mục không thể truy cập được, vì vậy chúng ta có thể thoát khỏi vòng lặp. Key vẫn còn hiệu lực cho đến khi: nó bị hủy bỏ bằng cách gọi phương cancel() của nó một cách tường minh, hoặc bị hủy bỏ mặc định khi đối tượng không thể truy cập được nữa hoặc bằng cách đóng WatchService.
- Đóng WatchService: WatchService sẽ thoát khi một trong hai luồng thoát hoặc khi nó được đóng (bằng cách gọi phương thức đóng của nó).
Lưu ý: API Watch Service không cho phép đăng ký file riêng lẻ. Chương trình sẽ ném ra một ngoại lệ java.nio.file.NotDirectoryException, nếu bạn đang cố gắng làm như vậy.
Ví dụ
Theo dõi sự thay đổi của một thư mục
Chương trình bên dưới minh họa việc theo dõi một thư mục khi có thay đổi với các sự kiện như thêm, sửa, xóa các file trong thư mục uploads.
package com.gpcoder.watchservice; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; public class DirectoryWatchExample { public static void main(String[] args) throws IOException { WatchService watcher = FileSystems.getDefault().newWatchService(); Path dir = Paths.get("C:/uploads"); dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); System.out.println("Watch Service registered for dir: " + dir.getFileName()); WatchKey key = null; while (true) { try { // System.out.println("Waiting for key to be signalled..."); key = watcher.take(); } catch (InterruptedException ex) { System.out.println("InterruptedException: " + ex.getMessage()); return; } for (WatchEvent<?> event : key.pollEvents()) { // Retrieve the type of event by using the kind() method. WatchEvent.Kind<?> kind = event.kind(); WatchEvent<Path> ev = (WatchEvent<Path>) event; Path fileName = ev.context(); if (kind == StandardWatchEventKinds.ENTRY_CREATE) { System.out.printf("A new file %s was created.%n", fileName.getFileName()); } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { System.out.printf("A file %s was modified.%n", fileName.getFileName()); } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { System.out.printf("A file %s was deleted.%n", fileName.getFileName()); } } boolean valid = key.reset(); if (!valid) { break; } } } }
Thực thi chương trình trên và thêm/ sửa/ xóa một vài file trong thư mục “C:/uploads”, bạn sẽ thấy kết quả như sau:
Watch Service registered for dir: uploads A new file test.txt was created. A file test.txt was modified. A new file image1.png was created. A file image1.png was modified. A new file image2.png was created. A file image2.png was modified. A file image2.png was deleted. A file test.txt was modified. A file test.txt was modified.
Theo dõi sự thay đổi của một thư mục, thư mục con và các tập tin
package com.gpcoder.watchservice; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; public class RecursiveWatchServiceExample { private final WatchService watcher; private final Map<WatchKey, Path> keys; public static void main(String[] args) throws IOException { Path dir = Paths.get("C:/uploads"); new RecursiveWatchServiceExample(dir).processEvents(); } /** * Creates a WatchService and registers the given directory */ private RecursiveWatchServiceExample(Path dir) throws IOException { this.watcher = FileSystems.getDefault().newWatchService(); this.keys = new HashMap<WatchKey, Path>(); walkAndRegisterDirectories(dir); } /** * Register the given directory with the WatchService; This function will be * called by FileVisitor */ private void registerDirectory(Path dir) throws IOException { WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); keys.put(key, dir); } /** * Register the given directory, and all its sub-directories, with the * WatchService. */ private void walkAndRegisterDirectories(final Path start) throws IOException { // register directory and sub-directories Files.walkFileTree(start, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { registerDirectory(dir); return FileVisitResult.CONTINUE; } }); } /** * Process all events for keys queued to the watcher */ private void processEvents() { for (;;) { // wait for key to be signalled WatchKey key; try { key = watcher.take(); } catch (InterruptedException x) { return; } Path dir = keys.get(key); if (dir == null) { System.err.println("WatchKey not recognized!!"); continue; } for (WatchEvent<?> event : key.pollEvents()) { @SuppressWarnings("rawtypes") WatchEvent.Kind kind = event.kind(); // Context for directory entry event is the file name of entry @SuppressWarnings("unchecked") Path name = ((WatchEvent<Path>) event).context(); Path child = dir.resolve(name); // print out event System.out.format("%s: %s\n", event.kind().name(), child); // if directory is created, and watching recursively, then register it and its // sub-directories if (kind == StandardWatchEventKinds.ENTRY_CREATE) { try { if (Files.isDirectory(child)) { walkAndRegisterDirectories(child); } } catch (IOException x) { // do something useful } } } // reset key and remove from set if directory no longer accessible boolean valid = key.reset(); if (!valid) { keys.remove(key); // all directories are inaccessible if (keys.isEmpty()) { break; } } } } }
Thực thi chương trình trên và thêm/ sửa/ xóa một vài file trong thư mục “C:/uploads”, bạn sẽ thấy kết quả như sau:
ENTRY_CREATE: C:\uploads\image1.png ENTRY_MODIFY: C:\uploads\image1.png ENTRY_MODIFY: C:\uploads\image1.png ENTRY_CREATE: C:\uploads\docs ENTRY_MODIFY: C:\uploads\docs ENTRY_CREATE: C:\uploads\images ENTRY_MODIFY: C:\uploads\images ENTRY_CREATE: C:\uploads\test.txt ENTRY_MODIFY: C:\uploads\test.txt ENTRY_DELETE: C:\uploads\image1.png ENTRY_DELETE: C:\uploads\test.txt ENTRY_CREATE: C:\uploads\docs\note.txt ENTRY_MODIFY: C:\uploads\docs\note.txt ENTRY_MODIFY: C:\uploads\docs ENTRY_CREATE: C:\uploads\images\avatar.png ENTRY_MODIFY: C:\uploads\images\avatar.png ENTRY_MODIFY: C:\uploads\images ENTRY_MODIFY: C:\uploads\images\avatar.png
Khi nào cần sử dụng Watch Service API
Watch Service API được thiết kế cho các ứng dụng cần được thông báo về các sự kiện thay đổi file. Nó thích hợp cho bất kỳ ứng dụng nào, như trình soạn thảo hoặc IDE, có khả năng có nhiều file đang mở và cần đảm bảo rằng các file được đồng bộ hóa với hệ thống file. Nó cũng rất thích hợp cho một máy chủ ứng dụng để theo dõi một thư mục, có thể chờ đợi các tệp .jsp hoặc .jar được thêm vào để triển khai, cài đặt chúng.
Tài liệu tham khảo:
- https://docs.oracle.com/javase/7/docs/api/java/nio/file/WatchService.html
- https://docs.oracle.com/javase/tutorial/essential/io/notification.html
- https://howtodoinjava.com/java8/java-8-watchservice-api-tutorial/