Моніторинг файлів за домопомогою Java NIO

  • 27 января, 12:45
  • 4654
  • 0

Невеликий урок про те, як за допомогою пакета java.nio.file написати клас для спостереження за змінами стану файлів в директорії.

Java NIO або Java New I / O це вкрай корисний пакет, що дозволяє використовувати асинхронний ввід / вивід. Сьогодні за допомогою java.nio.file, слідуючи паттерну Observer, ми реалізуємо свій клас для спостереження за станом файлів в папці. Наш план:

  1. Насамперед створимо WatchService.
  2. Потім змінну Path, яка вказує на папку, яку плануємо моніторити.
  3. Далі нескінченний цикл спостереження. Коли відбувається подія, що нас цікавить, клас WathKey поміщає її в чергу спостерігача. Після обробки події ми повинні повернути ключ в стан готовності, викликавши метод reset(). Якщо метод поверне false, то ключ більше не дійсний, цикл можна завершити.

WatchService watchService = FileSystems.getDefault().newWatchService();     Path path = Paths.get("c:\\directory");     path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);     boolean poll = true;     while (poll) {         WatchKey key = watchService.take();         for (WatchEvent<?> event : key.pollEvents()) {             System.out.println("Event kind : " + event.kind() + " - File : " + event.context());         }         poll = key.reset();     }

Даний код повинен вивести в консоль наступне:

Event kind : ENTRY_CREATE - File : file.txt Event kind : ENTRY_DELETE - File : file.txt Event kind : ENTRY_CREATE - File : test.txt Event kind : ENTRY_MODIFY - File : test.txt

Watch Service API досить низькорівнева штука, ми реалізуємо на його основі більш високорівневе API. Почнемо з написання класу FileEvent. Так як це об'єкт стану події, його необхідно успадкувати від EventObject і передати в конструктор посилання на файл, за яким будемо спостерігати.

FileEvent.java

import java.io.File; import java.util.EventObject; public class FileEvent extends EventObject {     public FileEvent(File file) {         super(file);     }     public File getFile() {         return (File) getSource();     } }

Тепер створюємо інтерфейс слухача FileListener, успадкований від java.util.EventListener. Цей інтерфейс повинні реалізувати всі слухачі, які будуть підписуватися на події нашої папки.

FileListener.java

import java.util.EventListener; public interface FileListener extends EventListener {     public void onCreated(FileEvent event);     public void onModified(FileEvent event);     public void onDeleted(FileEvent event); }

Нарешті, створюємо клас, який буде зберігати в собі список слухачів, підписаних на папку. Назвемо його FileWatcher.

public class FileWatcher {     protected List<FileListener> listeners = new ArrayList<>();     protected final File folder;     public FileWatcher(File folder) {         this.folder = folder;     }     public List<FileListener> getListeners() {         return listeners;     }     public FileWatcher setListeners(List<FileListener> listeners)          this.listeners = listeners;         return this;     } }

Варто реалізувати клас FileWatcher як Runnable, щоб мати можливість запускати спостереження, якщо зазначена папка існує.

FileWatcher.java

public class FileWatcher implements Runnable {     public void watch() {         if (folder.exists()) {             Thread thread = new Thread(this);             thread.setDaemon(true);             thread.start();         }     }     @Override     public void run() {     } }

Так як в методі run() будуть створюватися об'єкти WatchService, які використовують зовнішні ресурси (посилання на файли), всі події будемо зберігати в статичному списку. Така реалізація дозволить викликати метод close() з будь-якого потоку, що чекає ключ. А також викликати виключення ClosedWatchServiceException, щоб скасувати спостереження без витоку пам'яті.

@Override public void contextDestroyed(ServletContextEvent event) {         for (WatchService watchService : FileWatcher.getWatchServices()){             try {                 watchService.close();             } catch (IOException e) {}         } }

public class FileWatcher implements Runnable {     protected static final List<WatchService> watchServices = new ArrayList<>();     @Override     public void run() {         try (WatchService watchService = FileSystems.getDefault().newWatchService()) {             Path path = Paths.get(folder.getAbsolutePath());             path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);             watchServices.add(watchService);             boolean poll = true;             while (poll) {                 poll = pollEvents(watchService);             }         } catch (IOException | InterruptedException | ClosedWatchServiceException e) {             Thread.currentThread().interrupt();         }     }     protected boolean pollEvents(WatchService watchService) throws InterruptedException {         WatchKey key = watchService.take();         Path path = (Path) key.watchable();         for (WatchEvent<?> event : key.pollEvents()) {             notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile());         }         return key.reset();     }     public static List<WatchService> getWatchServices() {         return Collections.unmodifiableList(watchServices);     } }

Коли відбувається подія, що нас цікавить і шлях коректний, ми повідомляємо слухачів про подію. Якщо була створена нова директорія, то для неї буде ініційований новий екземпляр FileWatcher.

FileWatcher.java

public class FileWatcher implements Runnable {     protected void notifyListeners(WatchEvent.Kind<?> kind, File file) {         FileEvent event = new FileEvent(file);         if (kind == ENTRY_CREATE) {             for (FileListener listener : listeners) {                 listener.onCreated(event);             }             if (file.isDirectory()) {                 new FileWatcher(file).setListeners(listeners).watch();             }         }     else if (kind == ENTRY_MODIFY) {             for (FileListener listener : listeners) {                 listener.onModified(event);             }         }     else if (kind == ENTRY_DELETE) {             for (FileListener listener : listeners) {                 listener.onDeleted(event);             }         }     } }

Повна реалізація класу FileWatcher буде виглядати так:

FileWatcher.java

import static java.nio.file.StandardWatchEventKinds.*; import java.io.File; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class FileWatcher implements Runnable {     protected List<FileListener> listeners = new ArrayList<>();     protected final File folder;     protected static final List<WatchService> watchServices = new ArrayList<>();     public FileWatcher(File folder) {         this.folder = folder;     }     public void watch() {         if (folder.exists()) {             Thread thread = new Thread(this);             thread.setDaemon(true);             thread.start();         }     }     @Override     public void run() {         try (WatchService watchService = FileSystems.getDefault().newWatchService()) {             Path path = Paths.get(folder.getAbsolutePath());             path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);             watchServices.add(watchService);             boolean poll = true;             while (poll) {                 poll = pollEvents(watchService);             }         } catch (IOException | InterruptedException | ClosedWatchServiceException e) {             Thread.currentThread().interrupt();         }     }     protected boolean pollEvents(WatchService watchService) throws InterruptedException {         WatchKey key = watchService.take();         Path path = (Path) key.watchable();         for (WatchEvent<?> event : key.pollEvents()) {             notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile());         }         return key.reset();     }     protected void notifyListeners(WatchEvent.Kind<?> kind, File file) {         FileEvent event = new FileEvent(file);         if (kind == ENTRY_CREATE) {             for (FileListener listener : listeners) {                 listener.onCreated(event);             }             if (file.isDirectory()) {                 new FileWatcher(file).setListeners(listeners).watch();             }         }    else if (kind == ENTRY_MODIFY) {             for (FileListener listener : listeners) {                 listener.onModified(event);             }         }    else if (kind == ENTRY_DELETE) {                for (FileListener listener : listeners) {                listener.onDeleted(event);             }         }     }     public FileWatcher addListener(FileListener listener) {         listeners.add(listener);         return this;     }     public FileWatcher removeListener(FileListener listener) {         listeners.remove(listener);         return this;     }     public List<FileListener> getListeners() {         return listeners;     }     public FileWatcher setListeners(List<FileListener> listeners) {         this.listeners = listeners;         return this;     }     public static List<WatchService> getWatchServices() {         return Collections.unmodifiableList(watchServices);     } }

Останній штрих - створення класу FileAdapter, найпростішої реалізації інтерфейсу FileListener.

FileAdapter.java   

public abstract class FileAdapter implements FileListener {     @Override     public void onCreated(FileEvent event) {         //реалізація не передбачена     }     @Override     public void onModified(FileEvent event) {         //реалізація не передбачена     }     @Override     public void onDeleted(FileEvent event) {         //реалізація не передбаченв     } }

Щоб переконатися в працездатності написаного коду, можна використовувати нескладний клас FileWatcherTest.

FileWatcherTest.java           

import static org.junit.Assert.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; public class FileWatcherTest {     @Test     public void test() throws IOException, InterruptedException {         File folder = new File("src/test/resources");         final Map<String, String> map = new HashMap<>();         FileWatcher watcher = new FileWatcher(folder);         watcher.addListener(new FileAdapter() {             public void onCreated(FileEvent event) {                 map.put("file.created", event.getFile().getName());             }             public void onModified(FileEvent event) {                 map.put("file.modified", event.getFile().getName());             }             public void onDeleted(FileEvent event) {                 map.put("file.deleted", event.getFile().getName());             }         }).watch();         assertEquals(1, watcher.getListeners().size());         wait(2000);         File file = new File(folder + "/test.txt");         try(FileWriter writer = new FileWriter(file)) {             writer.write("Some String");         }         wait(2000);         file.delete();         wait(2000);         assertEquals(file.getName(), map.get("file.created"));         assertEquals(file.getName(), map.get("file.modified"));         assertEquals(file.getName(), map.get("file.deleted"));     }     public void wait(int time) throws InterruptedException {         Thread.sleep(time);     } }

Незважаючи на простоту, моніторинг - потужний інструмент автоматизації процесів. За допомогою них, наприклад, можна автоматично перезапускати скрипти на продакшені, якщо один з них був змінений. 

Джерело перекладу


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все