热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Java可以如何实现文件变动的监听的示例

本篇文章主要介绍了Java可以如何实现文件变动的监听的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

应用中使用logback作为日志输出组件的话,大部分会去配置 `logback.xml` 这个文件,而且生产环境下,直接去修改logback.xml文件中的日志级别,不用重启应用就可以生效 那么,这个功能是怎么实现的呢?

应用中使用logback作为日志输出组件的话,大部分会去配置 logback.xml 这个文件,而且生产环境下,直接去修改logback.xml文件中的日志级别,不用重启应用就可以生效

那么,这个功能是怎么实现的呢?

I. 问题描述及分析

针对上面的这个问题,首先抛出一个实际的case,在我的个人网站 Z+中,所有的小工具都是通过配置文件来动态新增和隐藏的,因为只有一台服务器,所以配置文件就简化的直接放在了服务器的某个目录下

现在的问题时,我需要在这个文件的内容发生变动时,应用可以感知这种变动,并重新加载文件内容,更新应用内部缓存

一个最容易想到的方法,就是轮询,判断文件是否发生修改,如果修改了,则重新加载,并刷新内存,所以主要需要关心的问题如下:

  1. 如何轮询?
  2. 如何判断文件是否修改?
  3. 配置异常,会不会导致服务不可用?(即容错,这个与本次主题关联不大,但又比较重要...)

II. 设计与实现

问题抽象出来之后,对应的解决方案就比较清晰了

  1. 如何轮询 ? --》 定时器 Timer, ScheduledExecutorService 都可以实现
  2. 如何判断文件修改? --》根据 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的源码注释说明

If any execution of the task encounters an exception, subsequent executions are suppressed.Otherwise, the task will only terminate via cancellation or termination of the executor. 即如果定时任务执行过程中遇到发生异常,则后面的任务将不再执行。

所以,使用这种姿势的时候,得确保自己的任务不会抛出异常,否则后面就没法玩了

对应的解决方法也比较简单,整个catch一下就好

III. 进阶版

前面是一个基础的实现版本了,当然在java圈,基本上很多常见的需求,都是可以找到对应的开源工具来使用的,当然这个也不例外,而且应该还是大家比较属性的apache系列

首先maven依赖


  commons-io
  commons-io
  2.6

主要是借助这个工具中的 FileAlterationObserver, FileAlterationListener, FileAlterationMonitor 三个类来实现相关的需求场景了,当然使用也算是很简单了,以至于都不太清楚可以再怎么去说明了,直接看下面从我的一个开源项目quick-alarm中拷贝出来的代码

public class PropertiesConfListenerHelper {

  public static boolean registerConfChangeListener(File file, Function> 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> func;

    public MyFileListener(Function> func) {
      this.func = func;
    }

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

针对上面的实现,简单说明几点:

  1. 这个文件监听,是以目录为根源,然后可以设置过滤器,来实现对应文件变动的监听
  2. 如上面registerConfChangeListener方法,传入的file是具体的配置文件,因此构建参数的时候,捞出了目录,捞出了文件名作为过滤
  3. 第二参数是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;
  }
}

从上面基本上一目了然,整个的实现逻辑了,和我们的第一种定时任务的方法不太一样,这儿直接使用线程,死循环,内部采用sleep的方式来来暂停,因此出现异常时,相当于直接抛出去了,这个线程就跪了

补充JDK版本

jdk1.7,提供了一个WatchService,也可以用来实现文件变动的监听,之前也没有接触过,才知道有这个东西,然后搜了一下使用相关,发现也挺简单的,看到有博文说明是基于事件驱动式的,效率更高,下面也给出一个简单的示例demo

@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<&#63;> 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来实现配置文件变动的监听,主要涉及到的就是两个点

  1. 如何轮询: 定时器(Timer, ScheduledExecutorService), 线程死循环+sleep
  2. 文件修改: File#lastModified

整体来说,这个实现还是比较简单的,无论是自定义实现,还是依赖 commos-io来做,都没太大的技术成本,但是需要注意的一点是:

  1. 千万不要在定时任务 or 文件变动的回调方法中抛出异常!!!

为了避免上面这个情况,一个可以做的实现是借助EventBus的异步消息通知来实现,当文件变动之后,发送一个消息即可,然后在具体的重新加载文件内容的方法上,添加一个 @Subscribe注解即可,这样既实现了解耦,也避免了异常导致的服务异常 (如果对这个实现有兴趣的可以评论说明)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Java EE CDI:解决依赖关系冲突的实例
    在本教程中,我们将探讨如何在Java EE的CDI(上下文和依赖注入)框架中有效解决依赖关系的冲突问题。通过学习如何使用限定符,您将能够为应用程序的不同客户端提供多种接口实现,并确保每个客户端都能正确调用其所需的实现。 ... [详细]
  • 在Linux系统上构建Web服务器的详细步骤
    本文详细介绍了如何在Linux系统上搭建Web服务器的过程,包括安装Apache、PHP和MySQL等关键组件,以及遇到的一些常见问题及其解决方案。 ... [详细]
  • ServletContext接口在Java Web开发中扮演着重要角色,它提供了一种方式来获取关于整个Web应用程序的信息。通过ServletContext,开发者可以访问初始化参数、共享数据以及应用资源。 ... [详细]
  • 本文详细介绍了JSP(Java Server Pages)的九大内置对象及其功能,探讨了JSP与Servlet之间的关系及差异,并提供了实际编码示例。此外,还讨论了网页开发中常见的编码转换问题以及JSP的两种页面跳转方式。 ... [详细]
  • Eclipse 中 JSP 开发环境配置指南
    本文详细介绍了如何在 Eclipse 集成开发环境中配置 JSP 运行环境,包括必要的软件下载、Tomcat 服务器的配置以及常见问题的解决方法。 ... [详细]
  • 本文探讨了大型服务端开发过程中常见的几个误区,包括异步任务处理不当、日志同步模式使用、网络操作未设置超时、缓存命中率及响应时间未统计、单一缓存模式、分布式缓存加锁不当以及团队管理上的误区,旨在帮助开发者避免这些常见错误。 ... [详细]
  • 本文将详细介绍如何在ThinkPHP6框架中实现多数据库的部署,包括读写分离的策略,以及如何通过负载均衡和MySQL同步技术优化数据库性能。 ... [详细]
  • 本文探讨了如何通过WebBrowser控件在用户点击输入框时自动显示图片验证码。该过程可能涉及JavaScript事件的触发与响应。 ... [详细]
  • APM(Application Performance Management,应用性能管理)对于提供互联网服务的企业至关重要。本文将深入探讨APM如何帮助识别和解决导致用户流失的技术问题,以及它在提升整体用户体验方面的作用。 ... [详细]
  • 在使用Maven进行项目构建时,由于依赖库的下载速度慢常常让人感到沮丧,这直接影响了开发效率和学习热情。幸运的是,阿里云提供了一个快速的国内镜像服务,能够显著提升Maven项目的构建速度。 ... [详细]
  • 详解Linux系统启动过程及/etc/rc.d与/etc/rc.d/init.d目录关系
    本文深入探讨了Linux系统启动流程、运行级别及其与/etc/rc.d和/etc/rc.d/init.d目录的关联,旨在帮助读者理解系统启动时各脚本和服务的加载机制。 ... [详细]
  • window下kafka的安装以及测试
    目录一、安装JDK(需要安装依赖javaJDK)二、安装Kafka三、测试参考在Windows系统上安装消息队列kafka一、安装JDKÿ ... [详细]
  • 本文详细介绍了如何使用 PHP 编程语言输出 99 乘法表,包括使用不同的循环结构如 do-while、for 循环等方法,并提供了具体的代码示例。 ... [详细]
  • 本文探讨了使用Filter作为控制器的优势,以及Servlet与Filter之间的主要差异。同时,详细解析了Servlet的工作流程及其生命周期,以及ServletConfig与ServletContext的区别与应用场景。 ... [详细]
  • Android中解析XML文件的实践指南
    本文详细介绍了在Android应用开发中解析XML文件的方法,包括从本地文件和网络资源获取XML文件的不同途径,以及使用DOM、SAX和PULL三种解析方式的具体实现。 ... [详细]
author-avatar
无终的流浪
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有