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

  • 27 января, 14:45
  • 3690
  • 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 Новости