前言
因为所从事行业的原因,经常涉及到文件跨网闸传输的需求,我们知道数据跨网闸传输有两种方式:数据库和文件方式,由于我们的数据涉及到图片流,所以我们的跨网闸方式是文件摆渡
,又会涉及到文件的上传、读取
,尤其文件读取以往实现方式是定时线程监控文件
是否有新增和变更,但是这样实现业务逻辑比较复杂,经常会出现bug,定位也不方便,所以想对这一块业务逻辑做一个优化;
思路
通过监听模式对文件的增删改查进行监控,这样业务逻辑比较简单,这里对常用的文件监听方式进行简单总结,涉及jdk自带的webservice
和common-io工具的FileAlterationListenerAdaptor
;
实现方式
1)webservice
在Java 7
中新增了java.nio.file.WatchService
,通过它可以实现文件变动的监听。WatchService
是基于操作系统的文件系统监控器,可以监控系统所有文件的变化,无需遍历、无需比较,是一种基于信号收发的监控,效率高。
package com.sk.test;
import java.io.IOException;
import java.nio.file.*;
public class WatchServiceDemo {
public static void main(String[] args) throws IOException {
// 这里的监听必须是目录
Path path = Paths.get("G:\\var\\test");
// 创建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 discarded
continue;
}
// 返回触发事件的文件或目录的路径(相对路径)
Path fileName = (Path) event.context();
System.out.println("文件更新: " + fileName);
}
// 每次调用WatchService的take()或poll()方法时需要通过本方法重置
if (!key.reset()) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过WatchService
监听文件的类型也变得更加丰富:ENTRY_CREATE
目标被创建ENTRY_DELETE
目标被删除ENTRY_MODIFY
目标被修改OVERFLOW
一个特殊的Event
,表示Event
被放弃或者丢失
执行结果:
Connected to the target VM, address: '127.0.0.1:1635', transport: 'socket'
文件更新: aaa
文件更新: aaa
文件更新: test.txt
缺点:
只能监听当前目录下的文件和目录,不能监视子目录,而且我们也看到监听只能算是准实时的,而且监听时间只能取API默认提供的三个值。
2)FileAlterationListenerAdaptor
commons-io
对实现文件监听的实现位于org.apache.commons.io.monitor
包下,基本使用流程如下:
a、自定义文件监听类并继承 FileAlterationListenerAdaptor
实现对文件与目录的创建、修改、删除事件的处理;
b、自定义文件监控类,通过指定目录创建一个观察者 FileAlterationObserver
;
c、向监视器添加文件系统观察器,并添加文件监听器;
d、调用并执行。
引入依赖:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
第一步:创建文件监听器。根据需要在不同的方法内实现对应的业务逻辑处理。
package com.sk.service;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import java.io.File;
public class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onStart(FileAlterationObserver observer) {
super.onStart(observer);
System.out.println("onStart");
}
@Override
public void onDirectoryCreate(File directory) {
System.out.println("新建:" + directory.getAbsolutePath());
}
@Override
public void onDirectoryChange(File directory) {
System.out.println("修改:" + directory.getAbsolutePath());
}
@Override
public void onDirectoryDelete(File directory) {
System.out.println("删除:" + directory.getAbsolutePath());
}
@Override
public void onFileCreate(File file) {
String compressedPath = file.getAbsolutePath();
System.out.println("新建:" + compressedPath);
if (file.canRead()) {
// TODO 读取或重新加载文件内容
System.out.println("文件变更,进行处理");
}
}
@Override
public void onFileChange(File file) {
String compressedPath = file.getAbsolutePath();
System.out.println("修改:" + compressedPath);
}
@Override
public void onFileDelete(File file) {
System.out.println("删除:" + file.getAbsolutePath());
}
@Override
public void onStop(FileAlterationObserver observer) {
super.onStop(observer);
System.out.println("onStop");
}
}
第二步:封装一个文件监控的工具类,核心就是创建一个观察者FileAlterationObserver
,将文件路径Path和监听器FileAlterationListener
进行封装,然后交给FileAlterationMonitor
。
package com.sk.service;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import java.io.File;
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();
}
}
第三步:调用并执行:
package com.sk.service;
public class FileRunner {
public static void main(String[] args) throws Exception {
FileMonitor fileMonitor = new FileMonitor(1000);
fileMonitor.monitor("G:\\var\\test", new FileListener());
fileMonitor.start();
}
}
执行程序,会发现每隔1秒输入一次日志。当文件发生变更时,也会打印出对应的日志:
Connected to the target VM, address: '127.0.0.1:1954', transport: 'socket'
onStart
onStop
onStart
修改:G:\var\test\test.txt
onStop
onStart
onStop
Disconnected from the target VM, address: '127.0.0.1:1954', transport: 'socket'
onStart
onStop
当然,对应的监听时间间隔,可以通过在创建FileMonitor
时进行修改。
该方案中监听器本身会启动一个线程定时处理。在每次运行时,都会先调用事件监听处理类的onStart
方法,然后检查是否有变动,并调用对应事件的方法;比如,onChange
文件内容改变,检查完后,再调用onStop
方法,释放当前线程占用的CPU资源
,等待下次间隔时间到了被再次唤醒运行。
监听器是基于文件目录为根源的,也可以可以设置过滤器,来实现对应文件变动的监听。过滤器的设置可查看FileAlterationObserver
的构造方法:
public FileAlterationObserver(String directoryName, FileFilter fileFilter, IOCase caseSensitivity) {
this(new File(directoryName), fileFilter, caseSensitivity);
}