『壹』 关于java的监听器
1、public void addWindowListener(WindowListener l)添加指定的窗口侦听器,以从此窗口接收窗口事件。如果 l 为 null,则不抛出任何异常,且不执行任何操作。
这个是API中的方法定义,此方法参数为接口WindowListener,任何实现该接口的类都可以作为参数。
2、public abstract class WindowAdapterimplements WindowListener, WindowStateListener, WindowFocusListener
接收窗口事件的抽象适配器类。此类中的方法为空。此类存在的目的是方便创建侦听器对象。
扩展此类可创建 WindowEvent 侦听器并为所需事件重写该方法。(如果要实现
WindowListener 接口,则必须定义该接口内的所有方法。此抽象类将所有方法都定义为
null,所以只需针对关心的事件定义方法。)
使用扩展的类可以创建侦听器对象,然后使用窗口的 addWindowListener
方法向该窗口注册侦听器。当通过打开、关闭、激活或停用、图标化或取消图标化而改变了窗口状态时,将调用该侦听器对象中的相关方法,并将
WindowEvent 传递给该方法。
3、如果我想在代码中一次性使用某个类(抽象类或具体类)或接口,可以使用匿名类的方式,这样不需自己定义一个My***类,然后再使用,比较方便。用法就是直接在new WindowAdapter()后面加入类定义,在其中实现或覆盖方法就可以了。
匿名类不是返回值,而是相当于new String(“hello”)这种的扩展形式。我觉得匿名类的最多用处就是加监听器时。
附上WindowAdapter源代码:
implementsWindowListener,WindowStateListener,WindowFocusListener
{
publicvoidwindowOpened(WindowEvente){}
publicvoidwindowClosing(WindowEvente){}
publicvoidwindowClosed(WindowEvente){}
publicvoidwindowIconified(WindowEvente){}
publicvoidwindowDeiconified(WindowEvente){}
publicvoidwindowActivated(WindowEvente){}
publicvoidwindowDeactivated(WindowEvente){}
publicvoidwindowStateChanged(WindowEvente){}
publicvoidwindowGainedFocus(WindowEvente){}
publicvoidwindowLostFocus(WindowEvente){}
}
『贰』 java中为什么要设置监听器,有什么用
猪哥解答:
1、private JButton jb=new JButton("按钮");这句话声明了一个按钮,名字叫jb。
2、jb.addActionListener(this);这里给jb那个按钮设置了监听,默认为点击触发,当然你写的这个监听有点怪异~
3、点击按钮jb触发监听处理方法actionPerformed,在这里可以做你想要的操作,你代码实现的是改变lab这个label标签的内容。
4、至于java中为什么要用监听,这就像银行装监控一样,监视你的一举一动,银行装监控是为了捕捉每个进银行的人的动作,预防危险的发生。
java中做监听同样是为了监视某个客户端动作用的,万一你给我搞破坏怎么办(监听的作用远不止如此),当然也像平时生活中不是所有的地方都要放监控,要不就没法过了,java中也不是所有的地方都要放监听,具体哪里要放监听,不该是在课本里学的,应该根据实际工厂、公司的需求来定。
『叁』 Java实现监听文件变化的三种方法,推荐第三种
背景在研究规则引擎时,如果规则以文件的形式存储,那么就需要监听指定的目录或文件来感知规则是否变化,进而进行加载。当然,在其他业务场景下,比如想实现配置文件的动态加载、日志文件的监听、FTP文件变动监听等都会遇到类似的场景。
本文给大家提供三种解决方案,并分析其中的利弊,建议收藏,以备不时之需。
方案一:定时任务 + File#lastModified这个方案是最简单,最能直接想到的解决方案。通过定时任务,轮训查询文件的最后修改时间,与上一次进行对比。如果发生变化,则说明文件已经修改,进行重新加载或对应的业务逻辑处理。
在上篇文章《JDK的一个Bug,监听文件变更要小心了》中已经编写了具体的实例,并且也提出了其中的不足。
这里再把实例代码贴出来:
public class FileWatchDemo { /*** 上次更新时间*/ public static long LAST_TIME = 0L; public static void main(String[] args) throws IOException {String fileName = "/Users/zzs/temp/1.txt";// 创建文件,仅为实例,实践中由其他程序触发文件的变更createFile(fileName);// 执行2次for (int i = 0; i < 2; i++) { long timestamp = readLastModified(fileName); if (timestamp != LAST_TIME) {System.out.println("文件已被更新:" + timestamp);LAST_TIME = timestamp;// 重新加载,文件内容 } else {System.out.println("文件未更新"); }} } public static void createFile(String fileName) throws IOException {File file = new File(fileName);if (!file.exists()) { boolean result = file.createNewFile(); System.out.println("创建文件:" + result);} } public static long readLastModified(String fileName) {File file = new File(fileName);return file.lastModified(); }}对于文件低频变动的场景,这种方案实现简单,基本上可以满足需求。不过像上篇文章中提到的那样,需要注意Java 8和Java 9中File#lastModified的Bug问题。
但该方案如果用在文件目录的变化上,缺点就有些明显了,比如:操作频繁,效率都损耗在遍历、保存状态、对比状态上了,无法充分利用OS的功能。
方案二:WatchService在Java 7中新增了java.nio.file.WatchService,通过它可以实现文件变动的监听。WatchService是基于操作系统的文件系统监控器,可以监控系统所有文件的变化,无需遍历、无需比较,是一种基于信号收发的监控,效率高。
public class WatchServiceDemo {public static void main(String[] args) throws IOException {// 这里的监听必须是目录Path path = Paths.get("/Users/zzs/temp/");// 创建WatchService,它是对操作系统的文件监视器的封装,相对之前,不需要遍历文件目录,效率要高很多WatchService watcher = FileSystems.getDefault().newWatchService();// 注册指定目录使用的监听器,监视目录下文件的变化;// PS:Path必须是目录,不能是文件;// StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);// 创建一个线程,等待目录下的文件发生变化try {while (true) {// 获取目录的变化:// take()是一个阻塞方法,会等待监视器发出的信号才返回。// 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。// 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个;WatchKey key = watcher.take();// 处理文件变化事件:// key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。for (WatchEvent<?> event : key.pollEvents()) {// event.kind():事件类型if (event.kind() == StandardWatchEventKinds.OVERFLOW) {//事件可能lost or discardedcontinue;}// 返回触发事件的文件或目录的路径(相对路径)Path fileName = (Path) event.context();System.out.println("文件更新: " + fileName);}// 每次调用WatchService的take()或poll()方法时需要通过本方法重置if (!key.reset()) {break;}}} catch (Exception e) {e.printStackTrace();}}}上述demo展示了WatchService的基本使用方式,注解部分也说明了每个API的具体作用。
通过WatchService监听文件的类型也变得更加丰富:
ENTRY_CREATE 目标被创建
ENTRY_DELETE 目标被删除
ENTRY_MODIFY 目标被修改
OVERFLOW 一个特殊的Event,表示Event被放弃或者丢失
如果查看WatchService实现类(PollingWatchService)的源码,会发现,本质上就是开启了一个独立的线程来监控文件的变化:
PollingWatchService() {// TBD: Make the number of threads configurablescheledExecutor = Executors.(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(null, r, "FileSystemWatcher", 0, false); t.setDaemon(true); return t; }});}也就是说,本来需要我们手动实现的部分,也由WatchService内部帮我们完成了。
如果你编写一个demo,进行验证时,会很明显的感觉到WatchService监控文件的变化并不是实时的,有时候要等几秒才监听到文件的变化。以实现类PollingWatchService为例,查看源码,可以看到如下代码:
void enable(Set<? extends Kind<?>> var1, long var2) {synchronized(this) {this.events = var1;Runnable var5 = new Runnable() {public void run() {PollingWatchKey.this.poll();}};this.poller = PollingWatchService.this.scheledExecutor.scheleAtFixedRate(var5, var2, var2, TimeUnit.SECONDS);}}也就是说监听器由按照固定时间间隔的调度器来控制的,而这个时间间隔在SensitivityWatchEventModifier类中定义:
public enum SensitivityWatchEventModifier implements Modifier {HIGH(2),MEDIUM(10),LOW(30);// ...}该类提供了3个级别的时间间隔,分别为2秒、10秒、30秒,默认值为10秒。这个时间间隔可以在path#register时进行传递:
path.register(watcher, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY},SensitivityWatchEventModifier.HIGH);相对于方案一,实现起来简单,效率高。不足的地方也很明显,只能监听当前目录下的文件和目录,不能监视子目录,而且我们也看到监听只能算是准实时的,而且监听时间只能取API默认提供的三个值。
该API在Stack Overflow上也有人提出Java 7在Mac OS下有延迟的问题,甚至涉及到Windows和Linux系统,笔者没有进行其他操作系统的验证,如果你遇到类似的问题,可参考对应的文章,寻求解决方案:https://blog.csdn.net/claram/article/details/97919664 。
方案三:Apache Commons-IO方案一我们自己来实现,方案二借助于JDK的API来实现,方案三便是借助于开源的框架来实现,这就是几乎每个项目都会引入的commons-io类库。
引入相应依赖:
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.7</version></dependency>注意,不同的版本需要不同的JDK支持,2.7需要Java 8及以上版本。
commons-io对实现文件监听的实现位于org.apache.commons.io.monitor包下,基本使用流程如下:
自定义文件监听类并继承 FileAlterationListenerAdaptor 实现对文件与目录的创建、修改、删除事件的处理;
自定义文件监控类,通过指定目录创建一个观察者 FileAlterationObserver;
向监视器添加文件系统观察器,并添加文件监听器;
调用并执行。
第一步:创建文件监听器。根据需要在不同的方法内实现对应的业务逻辑处理。
public class FileListener extends FileAlterationListenerAdaptor {@Overridepublic void onStart(FileAlterationObserver observer) {super.onStart(observer);System.out.println("onStart");}@Overridepublic void onDirectoryCreate(File directory) {System.out.println("新建:" + directory.getAbsolutePath());}@Overridepublic void onDirectoryChange(File directory) {System.out.println("修改:" + directory.getAbsolutePath());}@Overridepublic void onDirectoryDelete(File directory) {System.out.println("删除:" + directory.getAbsolutePath());}@Overridepublic void onFileCreate(File file) {String compressedPath = file.getAbsolutePath();System.out.println("新建:" + compressedPath);if (file.canRead()) {// TODO 读取或重新加载文件内容System.out.println("文件变更,进行处理");}}@Overridepublic void onFileChange(File file) {String compressedPath = file.getAbsolutePath();System.out.println("修改:" + compressedPath);}@Overridepublic void onFileDelete(File file) {System.out.println("删除:" + file.getAbsolutePath());}@Overridepublic void onStop(FileAlterationObserver observer) {super.onStop(observer);System.out.println("onStop");}}第二步:封装一个文件监控的工具类,核心就是创建一个观察者FileAlterationObserver,将文件路径Path和监听器FileAlterationListener进行封装,然后交给FileAlterationMonitor。
public class FileMonitor {private FileAlterationMonitor monitor;public FileMonitor(long interval) {monitor = new FileAlterationMonitor(interval);}/** * 给文件添加监听 * * @param path 文件路径 * @param listener 文件监听器 */public void monitor(String path, FileAlterationListener listener) {FileAlterationObserver observer = new FileAlterationObserver(new File(path));monitor.addObserver(observer);observer.addListener(listener);}public void stop() throws Exception {monitor.stop();}public void start() throws Exception {monitor.start();}}第三步:调用并执行:
public class FileRunner {public static void main(String[] args) throws Exception {FileMonitor fileMonitor = new FileMonitor(1000);fileMonitor.monitor("/Users/zzs/temp/", new FileListener());fileMonitor.start();}}执行程序,会发现每隔1秒输入一次日志。当文件发生变更时,也会打印出对应的日志:
public class WatchServiceDemo {public static void main(String[] args) throws IOException {// 这里的监听必须是目录Path path = Paths.get("/Users/zzs/temp/");// 创建WatchService,它是对操作系统的文件监视器的封装,相对之前,不需要遍历文件目录,效率要高很多WatchService watcher = FileSystems.getDefault().newWatchService();// 注册指定目录使用的监听器,监视目录下文件的变化;// PS:Path必须是目录,不能是文件;// StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);// 创建一个线程,等待目录下的文件发生变化try {while (true) {// 获取目录的变化:// take()是一个阻塞方法,会等待监视器发出的信号才返回。// 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。// 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个;WatchKey key = watcher.take();// 处理文件变化事件:// key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。for (WatchEvent<?> event : key.pollEvents()) {// event.kind():事件类型if (event.kind() == StandardWatchEventKinds.OVERFLOW) {//事件可能lost or discardedcontinue;}// 返回触发事件的文件或目录的路径(相对路径)Path fileName = (Path) event.context();System.out.println("文件更新: " + fileName);}// 每次调用WatchService的take()或poll()方法时需要通过本方法重置if (!key.reset()) {break;}}} catch (Exception e) {e.printStackTrace();}}}0当然,对应的监听时间间隔,可以通过在创建FileMonitor时进行修改。
该方案中监听器本身会启动一个线程定时处理。在每次运行时,都会先调用事件监听处理类的onStart方法,然后检查是否有变动,并调用对应事件的方法;比如,onChange文件内容改变,检查完后,再调用onStop方法,释放当前线程占用的CPU资源,等待下次间隔时间到了被再次唤醒运行。
监听器是基于文件目录为根源的,也可以可以设置过滤器,来实现对应文件变动的监听。过滤器的设置可查看FileAlterationObserver的构造方法:
public class WatchServiceDemo {public static void main(String[] args) throws IOException {// 这里的监听必须是目录Path path = Paths.get("/Users/zzs/temp/");// 创建WatchService,它是对操作系统的文件监视器的封装,相对之前,不需要遍历文件目录,效率要高很多WatchService watcher = FileSystems.getDefault().newWatchService();// 注册指定目录使用的监听器,监视目录下文件的变化;// PS:Path必须是目录,不能是文件;// StandardWatchEventKinds.ENTRY_MODIFY,表示监视文件的修改事件path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);// 创建一个线程,等待目录下的文件发生变化try {while (true) {// 获取目录的变化:// take()是一个阻塞方法,会等待监视器发出的信号才返回。// 还可以使用watcher.poll()方法,非阻塞方法,会立即返回当时监视器中是否有信号。// 返回结果WatchKey,是一个单例对象,与前面的register方法返回的实例是同一个;WatchKey key = watcher.take();// 处理文件变化事件:// key.pollEvents()用于获取文件变化事件,只能获取一次,不能重复获取,类似队列的形式。for (WatchEvent<?> event : key.pollEvents()) {// event.kind():事件类型if (event.kind() == StandardWatchEventKinds.OVERFLOW) {//事件可能lost or discardedcontinue;}// 返回触发事件的文件或目录的路径(相对路径)Path fileName = (Path) event.context();System.out.println("文件更新: " + fileName);}// 每次调用WatchService的take()或poll()方法时需要通过本方法重置if (!key.reset()) {break;}}} catch (Exception e) {e.printStackTrace();}}}1小结至此,基于Java实现监听文件变化的三种方案便介绍完毕。经过上述分析及实例,大家已经看到,并没有完美的解决方案,根据自己的业务情况及系统的容忍度可选择最适合的方案。而且,在此基础上可以新增一些其他的辅助措施,来避免具体方案中的不足之处。
博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢迎关注~
技术交流:请联系博主微信号:zhuan2quan
原文:https://juejin.cn/post/7103318602748526628『肆』 有趣的 WatchService
欢迎来到本文,我们将深入探讨有趣且实用的 Java WatchService 接口。首先,让我们了解它的基本概念和用途,以及如何在开发中充分利用它。
WatchService 是 JDK 7 引入的一个接口,其核心功能在于监视注册对象的改变事件。例如,一个文件管理系统可以利用 WatchService 监视目录,以便实时更新文件列表,当文件被创建或删除时。
理解 WatchService 的基础概念后,接下来我们来学习几个关键的 API:
WatchService
WatchService 实现了对对象变化的监视,允许监听者注册感兴趣的路径和事件类型。
WatchKey
当注册的路径发生改变时,WatchService 会生成 WatchKey 对象,用于通知监听者。
WatchEvent
WatchEvent 代表路径上的一个事件,如目录下文件的创建、删除等。
StandardWatchEventKinds
定义了常见的事件类型,如文件创建、修改、删除等。
在实际应用中,WatchService 通过注册监听器与特定目录关联,监听器将收到 WatchKey 对象,通过它可以获取到最新的 WatchEvent。这种实时更新机制使得文件管理系统、日志系统等在处理实时数据时更为高效。
了解 WatchService 的工作原理后,我们可以通过以下步骤来应用它:
使用 WatchService 可以简化文件系统操作,提高应用的响应速度。它适用于各种需要实时监控文件系统变化的应用场景,如实时文件监控、日志分析等。
总结来说,WatchService 是一个强大的 Java 接口,通过它,开发者能够实现对文件系统变更的实时监控,为应用带来更高的性能和便利性。