Работа мечты в один клик 💼

💭Мечтаешь работать в Сбере, но не хочешь проходить десять кругов HR-собеседований? Теперь это проще, чем когда-либо!
💡AI-интервью за 15 минут – и ты уже на шаг ближе к своей новой работе.
Как получить оффер? 📌 Зарегистрируйся 📌 Пройди AI-интервью 📌 Получи обратную связь сразу же!
HR больше не тянут время – рекрутеры свяжутся с тобой в течение двух дней! 🚀
Реклама. ПАО СБЕРБАНК, ИНН 7707083893. Erid 2VtzquscAwp
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
и передать в конструктор ссылку на файл, за котором будем наблюдать.
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
. Этот интерфейс должны реализовать все слушатели, которые будут подписываться на события нашей папки.
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
, чтобы иметь возможность запускать наблюдение в виде демона, если указанная папка существует.
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
.
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()) {
// создаем новый FileWatcher для отслеживания новой директории
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
будет выглядеть так:
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
.
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
.
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);
}
}
Несмотря на кажущуюся простоту, наблюдатели – мощный инструмент автоматизации процессов. С помощью них, например, можно автоматически перезапускать скрипты на продакшене, если один из них был изменён. Уменьшение человеческого фактора – благо для программиста.
А используете ли вы наблюдателей в своей работе? Или, может быть, реактивное программирование?
Комментарии