Ứng dụng

  • Được áp dụng trong một số hệ thống cần chức năng nhận biết sự thay đổi khi thêm các directory, file trong một directory cụ thể.
  • Một hệ thống monitoring những thay đổi: create, edit, delete. Và xử lý với những sự thay đổi đó.

WatchService

Là một API của Java thực hiện monitor sự thay đổi của 1 directory cụ thể!

Các xử lý chính của API:

  1. Tạo một WatchService.
  2. đăng ký các directory muốn xem sự thay đổi, và đăng ký với WatchServer. Đăng ký thêm các hành vi để thông báo. Các hành vi bao gồm: ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY.
  3. Thực hiện một loop vô hạn để lắng nghe các hành vi trên các directory đã đăng ký. Các hành vi được được sinh ra được gọi là Key, và được đưa vào watcher queue.
  4. Lấy các Key từ trong watcher queue và xử lý cần thiết với sự thay đổi. Thông tin về sự thay đổi, file hoặc folder thay đổi cũng được lưu trong object Key.
  5. Reset Key và tiếp tục chờ hành vi tiếp.
  6. Close Service.

Code Sample

  1. Tạo một WatchService
WatchService watcher = FileSystems.getDefault().newWatchService();
  1. Đăng ký các action để monitor trên một Directory: ví dụ dưới đăng ký 3 action:
Path dir = Paths.get("/tmp/testWatcher");
WatchKey key = dir.register(watcher,ENTRY_CREATE,ENTRY_DELETE,ENTRY_MODIFY);
  1. Thực hiện loop để xử lý khi nhận các action
// Start the infinite polling loop
WatchKey key = null;
while (true) {
    key = service.take();

    // Dequeueing events
    Kind<?> kind = null;
    for (WatchEvent<?> watchEvent : key.pollEvents()) {
        // Get the type of the event
        kind = watchEvent.kind();
        if (OVERFLOW == kind) {
            continue; // loop
        } 
        WatchEvent<Path> ev = cast(watchEvent);
        Path name = ev.context();
        Path child = path.resolve(name);
        
        // print out event
        System.out.format("%s: %s\n", watchEvent.kind().name(), child);
        
        if (ENTRY_CREATE == kind) {
            // Output
            System.out.println("New path Created");
        } else if (ENTRY_MODIFY == kind) {
            // Output
            System.out.println("Modified: ");
        } else if (ENTRY_DELETE == kind) {
            // Output
            System.out.println("Delete: ");
        }
    }

    if (!key.reset()) {
        break; // loop
    }
}

Kết quả:

526  touch foo.txt
527  vi foo.txt 
528  touch bar.txt
529  rm bar.txt 
530  mkdir testFolder
531  cd testFolder/
532  touch foo2.txt
533  vi foo2.txt
Watching path: /tmp/testWatcher
ENTRY_CREATE: /tmp/testWatcher/foo.txt
New path Created
ENTRY_MODIFY: /tmp/testWatcher/foo.txt
Modified: 
ENTRY_CREATE: /tmp/testWatcher/bar.txt
New path Created
ENTRY_DELETE: /tmp/testWatcher/bar.txt
Delete: 
ENTRY_CREATE: /tmp/testWatcher/testFolder
New path Created
ENTRY_MODIFY: /tmp/testWatcher/testFolder
Modified: 
ENTRY_MODIFY: /tmp/testWatcher/testFolder
Modified: 
ENTRY_MODIFY: /tmp/testWatcher/testFolder
Modified: 

Đánh giá.

  • Với folder: /tmp/testWatcher thì mọi hành vi trong folder này như create, modify, delete đều được WatchService nhận biết và đưa ra log.
  • Với folder con thì các action không nhận biết được.
  • Vậy để có thể biết được sự thay đổi của các folder con bên trong thì các folder con cũng cần đăng ký với watcher như ở bước 2. Vì vậy cần duyệt folder. Để duyệt folder thì dùng thêm Files.walkFileTree

Walking the File Tree

  • Là chức năng duyệt một lượt tất cả các file, folder trong hệ thống bên trong một folder cụ thể.
  • Thực thi một interface: java.nio.file.FileVisitor
  • Có 4 phương thức có thể ghi đè lên FileVisitor:
    • preVisitDirectory: thực hiện trước khi visit Directory
    • postVisitDirectory: thực hiện khi đã visit Directory
    • visitFile: Thực hiện khi đã visit File
    • visitFileFailed: Thực hiện xử lý khi không thể visit File

Sample:

File dir = new File("/tmp/testWatcher");
Path start = dir.toPath();
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
        throws IOException
    {
        System.out.println("PreVisit Directory:" + dir);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult postVisitDirectory(Path dir,  IOException attrs)
        throws IOException
    {
        System.out.println("Visited Directory:" + dir);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult visitFile(Path dir, BasicFileAttributes attrs)
        throws IOException
    {
        if(attrs.isRegularFile()) {
            System.out.println("Visit File:" + dir);
        }
        
        return FileVisitResult.CONTINUE;
    }
    
});

Kết quả:

  • Folder test tree
$ tree -a testWatcher
testWatcher
├── .pm
├── foo.txt
└── testFolder
    └── foo2.txt
  • Chương trình
PreVisit Directory:/tmp/testWatcher
Visit File:/tmp/testWatcher/.pm
Visit File:/tmp/testWatcher/foo.txt
PreVisit Directory:/tmp/testWatcher/testFolder
Visit File:/tmp/testWatcher/testFolder/foo2.txt
Visited Directory:/tmp/testWatcher/testFolder
Visited Directory:/tmp/testWatcher

Đánh giá:

  • Như vậy Files.walkFileTree có thể duyệt được tất cả các file, folder trong 1 folder cố định.
  • **Việc kết hợp giữa Files.walkFileTree và WatchService có thể tạo ra một ứng dụng có thể monitor toàn bộ một folder và các folder con, cháu... **

Code & Result

Ý tưởng của việc kết hợp:

  1. Với một folder chỉ định, dùng Files.walkFileTree để đăng ký các folder con, cháu... của Folder chỉ định.
  2. Đăng ký các folder với WatchService
  3. WatchService lắng nghe các action trên các folder.
  4. Khi có một folder mới được tạo, thì dùng Files.walkFileTree đăng ký folder con của folder đó với watchService.

Code Sample:

Kết quả:

549  touch bar.txt
550  mkdir testFolder2/testFolder22
551  mkdir -p testFolder2/testFolder22
552  cd testFolder2
553  touch foo3.txt
554  vi foo3.txt 
555  cd testFolder22/
556  touch foo33.txt
557  touch bar33.txt
558  rm -rf bar33.txt
Scanning /tmp/testWatcher ...
Done.
ENTRY_CREATE: /tmp/testWatcher/bar.txt
ENTRY_CREATE: /tmp/testWatcher/testFolder2
register: /tmp/testWatcher/testFolder2
register: /tmp/testWatcher/testFolder2/testFolder22
ENTRY_MODIFY: /tmp/testWatcher/testFolder2
ENTRY_CREATE: /tmp/testWatcher/testFolder2/foo3.txt
ENTRY_MODIFY: /tmp/testWatcher/testFolder2
ENTRY_MODIFY: /tmp/testWatcher/testFolder2/foo3.txt
ENTRY_MODIFY: /tmp/testWatcher/testFolder2/testFolder22
ENTRY_CREATE: /tmp/testWatcher/testFolder2/testFolder22/foo33.txt
ENTRY_MODIFY: /tmp/testWatcher/testFolder2/testFolder22
ENTRY_CREATE: /tmp/testWatcher/testFolder2/testFolder22/bar33.txt
ENTRY_MODIFY: /tmp/testWatcher/testFolder2/testFolder22
ENTRY_DELETE: /tmp/testWatcher/testFolder2/testFolder22/bar33.txt

Tham Khảo