Как Java может реализовать мониторинг изменений файлов

Java

Как Java может реализовать мониторинг изменений файлов

Если logback используется в качестве компонента вывода журнала в приложении, большинство из них будет настроеноlogback.xmlЭтот файл и рабочая среда напрямую изменяют уровень журнала в файле logback.xml, и это вступает в силу без перезапуска приложения.

Итак, как достигается эта функция?

I. Описание и анализ проблемы

В ответ на вышеуказанную проблему, сначала закиньте реальный кейс на мой личный сайтZ+, все гаджеты динамически добавляются и скрываются через файлы конфигурации.Поскольку сервер только один, файлы конфигурации упрощены и размещены непосредственно в определенной директории сервера.

Текущая проблема заключается в том, что когда содержимое этого файла изменяется, приложение может воспринять это изменение, перезагрузить содержимое файла и обновить внутренний кеш приложения.

Один из самых простых способов — это опрос, чтобы определить, был ли изменен файл.Если он был изменен, он будет перезагружен и память будет обновлена.Поэтому основные проблемы, о которых следует беспокоиться, заключаются в следующем:

  • Как провести опрос?
  • Как узнать, был ли изменен файл?
  • Если конфигурация неверна, служба будет недоступна? (То есть отказоустойчивость, это не относится к этой теме, но важнее...)

II. Разработка и реализация

После того, как проблема абстрагирована, соответствующее решение становится более ясным.

  • Как провести опрос? --" Таймер Таймер, ScheduledExecutorService может быть реализован
  • Как судить о модификации файла? --"в соответствии сjava.io.File#lastModifiedПолучите время последней модификации файла и сравните его

Тогда очень простая реализация проще:

public class FileUpTest {

    private long lastTime;

    @Test
    public void testFileUpdate() {
        File file = new File("/tmp/alarmConfig");

        // 首先文件的最近一次修改时间戳
        lastTime = file.lastModified();

        // 定时任务,每秒来判断一下文件是否发生变动,即判断lastModified是否改变
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (file.lastModified() > lastTime) {
                    System.out.println("file update! time : " + file.lastModified());
                    lastTime = file.lastModified();
                }
            }
        },0, 1, TimeUnit.SECONDS);


        try {
            Thread.sleep(1000 * 60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Вышеупомянутая очень простая и базовая реализация, и она в основном может удовлетворить наши потребности, так в чем проблема с этой реализацией?

Что произойдет, если во время выполнения запланированной задачи возникнет исключение?

Небольшая модификация кода выше

public class FileUpTest {

    private long lastTime;

    private void ttt() {
        throw new NullPointerException();
    }

    @Test
    public void testFileUpdate() {
        File file = new File("/tmp/alarmConfig");

        lastTime = file.lastModified();

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (file.lastModified() > lastTime) {
                    System.out.println("file update! time : " + file.lastModified());
                    lastTime = file.lastModified();
                    ttt();
                }
            }
        }, 0, 1, TimeUnit.SECONDS);


        try {
            Thread.sleep(1000 * 60 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

В реальном тесте обнаружено, что приведенный выше код срабатывает только при его первом изменении, но он не действует при повторном изменении, то есть при возникновении исключения запланированная задача больше не будет выполняться. быть выполнен. Основная причина этой проблемы заключается в том, чтоScheduledExecutorServiceпричина

Просмотрите описание комментария к исходному коду ScheduledExecutorService напрямую.

Если какое-либо выполнение задачи сталкивается с исключением, последующие выполнения подавляются, в противном случае задача будет завершена только путем отмены или завершения исполнителя. То есть, если во время выполнения запланированной задачи возникнет исключение, последующие задачи выполняться больше не будут.

Итак, при использовании этой позы убедитесь, что ваша задача не выбрасывает исключения, иначе вы не сможете играть позже.

Соответствующее решение также относительно простое, просто поймайте весь

III Расширенное издание

Передняя часть представляет собой базовую версию реализации.Конечно, в кругу Java в основном можно найти много общих потребностей с соответствующими инструментами с открытым исходным кодом для использования.Конечно, это не исключение, и это должна быть серия Apache, которую все сравнивают.

Первая зависимость maven

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

В основном с помощью этого инструментаFileAlterationObserver, FileAlterationListener, FileAlterationMonitorЕсть три класса для реализации соответствующих сценариев спроса.Конечно, использование очень простое, так что я не знаю, как это объяснить.Достаточно посмотреть на код, скопированный из одного из моих проектов с открытым исходным кодом, quick-alarm.

public class PropertiesConfListenerHelper {

    public static boolean registerConfChangeListener(File file, Function<File, Map<String, AlarmConfig>> func) {
        try {
            // 轮询间隔 5 秒
            long interval = TimeUnit.SECONDS.toMillis(5);


            // 因为监听是以目录为单位进行的,所以这里直接获取文件的根目录
            File dir = file.getParentFile();

            // 创建一个文件观察器用于过滤
            FileAlterationObserver observer = new FileAlterationObserver(dir,
                    FileFilterUtils.and(FileFilterUtils.fileFileFilter(),
                            FileFilterUtils.nameFileFilter(file.getName())));

            //设置文件变化监听器
            observer.addListener(new MyFileListener(func));
            FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
            monitor.start();

            return true;
        } catch (Exception e) {
            log.error("register properties change listener error! e:{}", e);
            return false;
        }
    }


    static final class MyFileListener extends FileAlterationListenerAdaptor {

        private Function<File, Map<String, AlarmConfig>> func;

        public MyFileListener(Function<File, Map<String, AlarmConfig>> func) {
            this.func = func;
        }

        @Override
        public void onFileChange(File file) {
            Map<String, AlarmConfig> ans = func.apply(file); // 如果加载失败,打印一条日志
            log.warn("PropertiesConfig changed! reload ans: {}", ans);
        }
    }
}

Для приведенной выше реализации кратко поясним несколько моментов:

  • Этот мониторинг файлов находится в каталоге, а затем можно установить фильтры для отслеживания соответствующих изменений файлов.
  • как указано вышеregisterConfChangeListenerметод, входящий файл является специфическим файлом конфигурации, поэтому при построении параметров директория и имя файла выносятся в качестве фильтра
  • Второй параметр — это синтаксис jdk8, который представляет собой конкретное содержимое файла конфигурации чтения и сопоставляется с соответствующим объектом сущности.

Один вопрос: что произойдет, если при выполнении метода func возникнет исключение?

Фактические результаты теста такие же, как и выше.После создания исключения вы все еще стоите на коленях, поэтому вам все равно нужно следить за тем, чтобы не запускать исключения.

Затем просто взгляните на приведенную выше логику реализации и напрямую вычтите основной модуль.

public void run() {
    while(true) {
        if(this.running) {
            Iterator var1 = this.observers.iterator();

            while(var1.hasNext()) {
                FileAlterationObserver observer = (FileAlterationObserver)var1.next();
                observer.checkAndNotify();
            }

            if(this.running) {
                try {
                    Thread.sleep(this.interval);
                } catch (InterruptedException var3) {
                    ;
                }
                continue;
            }
        }

        return;
    }
}

Из вышеизложенного с первого взгляда становится ясно, что вся логика реализации отличается от нашего первого метода синхронизации задач.Здесь потоки используются напрямую, бесконечные циклы и сон используется внутри для приостановки.Поэтому, когда возникает исключение , это равносильно Выкинуть прямо, эта нить встанет на колени


JDK-версия

jdk1.7, предоставляетWatchService,тоже можно использовать для мониторинга изменений файлов.Раньше никогда к нему не прикасался.Просмотрел инструкцию,а потом искал подходящее применение.Обнаружил,что это довольно просто.Также привожу простой пример демо.

@Test
public void testFileUpWather() throws IOException {
    // 说明,这里的监听也必须是目录
    Path path = Paths.get("/tmp");
    WatchService watcher = FileSystems.getDefault().newWatchService();
    path.register(watcher, ENTRY_MODIFY);

    new Thread(() -> {
        try {
            while (true) {
                WatchKey key = watcher.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    if (event.kind() == OVERFLOW) {
                        //事件可能lost or discarded 
                        continue;
                    }
                    Path fileName = (Path) event.context();
                    System.out.println("文件更新: " + fileName);
                }
                if (!key.reset()) { // 重设WatchKey
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();


    try {
        Thread.sleep(1000 * 60 * 10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

IV. Резюме

Использование Java для мониторинга изменений файла конфигурации в основном включает два момента.

  • Как опрашивать: Таймер (Timer, ScheduledExecutorService), бесконечный цикл потока + сон
  • Модификация файла: File#lastModified

В целом, эта реализация относительно проста.Будь то пользовательская реализация или использование commos-io для ее выполнения, технических затрат не так много, но следует отметить следующее:

  • Никогда не создавайте исключение в методе обратного вызова запланированной задачи или изменения файла! ! !

Чтобы избежать описанной выше ситуации, возможной реализацией является использование асинхронного уведомления о сообщении EventBus.Когда файл изменяется, отправьте сообщение, а затем добавьте определенный метод для перезагрузки содержимого файла.@SubscribeДостаточно аннотации, которая не только обеспечивает развязку, но и позволяет избежать сервисных исключений, вызванных исключениями (если вам интересна эта реализация, вы можете прокомментировать и объяснить)

В. Другое

Справочный проект

утверждение

Это не так хорошо, как письмо веры.Добавлено содержание, которое является исключительно мнением семьи.Из-за моих средних способностей мое мнение неполно.Если у вас есть какие-либо вопросы, пожалуйста, критикуйте и исправьте меня.

Сканируйте внимание, делитесь java

QrCode