Невеликий урок про те, як за допомогою пакета java.nio.file написати клас для спостереження за змінами стану файлів в директорії.
Java NIO або Java New I / O це вкрай корисний пакет, що дозволяє використовувати асинхронний ввід / вивід. Сьогодні за допомогою java.nio.file, слідуючи паттерну Observer, ми реалізуємо свій клас для спостереження за станом файлів в папці. Наш план:
- Насамперед створимо WatchService.
- Потім змінну Path, яка вказує на папку, яку плануємо моніторити.
- Далі нескінченний цикл спостереження. Коли відбувається подія, що нас цікавить, клас 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 комментариев
Добавить комментарий