(转)通过Java SE 7自带的监控服务(WatchService API)实现类似.NET FileWatcher的功能
Java SE 7 Tutorial中增加了一个监控目录变更情况的示例,用于介绍其新发布的WatchService API。
但对于用惯了.NET FileWatcher的用户而言,如果用于项目我认为它有两个欠缺:
1、应该提供一个独立线程后台运行机制,让这个监控过程自己在后台转,不影响前端处理
2、 Java不像.NET有内置的源生事件机制,不过可以借助它内置的Observer/Observable对象用观察者模式实现准事件
下面是把Java SE Tutorial示例中无关内容删除,补充上述两个扩展后的实现,因为这个API比较新,也希望能和大家多多探讨:
1、参考.NET定义事件参数对象
package marvellousworks.practicalpattern.concept.unittest; import java.nio.file.WatchEvent.Kind; /** * 文件系统事件类型 * @author wangxiang * */ public final class FileSystemEventArgs { private final String fileName; private final Kind<?> kind; public FileSystemEventArgs(String fileName, Kind<?> kind){ this.fileName = fileName; this.kind = kind; } /** * 文件的路径 */ public String getFileName(){return fileName;} /** * 操作类型:变更、创建、删除 */ @SuppressWarnings("rawtypes") public Kind getKind(){return kind;} }
2、定义DirectoryWatcher,用于监控某个文件夹,至于如何扩展FileWatcher则可以在这个基础上通过限定文件名称和操作类型的方式扩展
package marvellousworks.practicalpattern.concept.unittest; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchEvent.Kind; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.Observable; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import static java.nio.file.StandardWatchEventKinds.*; /** * 监控一个目录内文件的更新、创建和删除事件(不包括子目录) * * 对于http://download.oracle.com/javase/tutorial/essential/io/notification.html进行了改造 * 使其更接近.NET的DirectoryWatcher使用习惯 * * 由于java没有类似.NET源生的事件机制 * 因此实现上采用了Java SE自带的Observer/Observable对象对外抛出“假”事件 * * 适于Java SE 7 * * @author wangxiang * */ public class DirectoryWatcher extends Observable{ private WatchService watcher; private Path path; private WatchKey key; private Executor executor = Executors.newSingleThreadExecutor(); FutureTask<Integer> task = new FutureTask<Integer>( new Callable<Integer>(){ public Integer call() throws InterruptedException{ processEvents(); return Integer.valueOf(0);}}); @SuppressWarnings("unchecked") static <T> WatchEvent<T> cast(WatchEvent<?> event) { return (WatchEvent<T>) event; } public DirectoryWatcher(String dir) throws IOException { watcher = FileSystems.getDefault().newWatchService(); path = Paths.get(dir); // 监控目录内文件的更新、创建和删除事件 key = path.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE); } /** * 启动监控过程 */ public void execute(){ // 通过线程池启动一个额外的线程加载Watching过程 executor.execute(task); } /** * 关闭后的对象无法重新启动 * @throws IOException */ public void shutdown() throws IOException { watcher.close(); executor = null; } /** * 监控文件系统事件 */ void processEvents() { while (true) { // 等待直到获得事件信号 WatchKey signal; try { signal = watcher.take(); } catch (InterruptedException x) { return; } for (WatchEvent<?> event : signal.pollEvents()) { Kind<?> kind = event.kind(); // TBD - provide example of how OVERFLOW event is handled if (kind == OVERFLOW) { continue; } // Context for directory entry event is the file name of entry WatchEvent<Path> ev = cast(event); Path name = ev.context(); notifiy(name.getFileName().toString(), kind); } // 为监控下一个通知做准备 key.reset(); } } /** * 通知外部各个Observer目录有新的事件更新 */ void notifiy(String fileName, Kind<?> kind){ // 标注目录已经被做了更改 setChanged(); // 主动通知各个观察者目标对象状态的变更 // 这里采用的是观察者模式的“推”方式 notifyObservers(new FileSystemEventArgs(fileName, kind)); } }
3、单元测试
package com.ieslab.web.tool; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; import org.junit.Assert; import org.junit.Test; public class DirectoryWatcherFixture { private static final String DIR_PATH =System.getProperty("user.dir"); private static final File DIR = new File(DIR_PATH); private static final String SUFFIX = ".txt"; private static final String PREFIX = "test"; private static final int ADD_TIMES = 3; /** * 观察者 * @author wangxiang * */ public class Logger implements Observer{ @Override public void update(Observable observable, Object eventArgs) { FileSystemEventArgs args = (FileSystemEventArgs) eventArgs; System.out.printf("%s has been %s\n", args.getFileName(), args.getKind()); Assert.assertTrue(args.getFileName().startsWith(PREFIX)); Assert.assertEquals(ENTRY_CREATE, args.getKind()); } } @Test public void testWatchFile() throws IOException, InterruptedException{ DirectoryWatcher watcher = new DirectoryWatcher(DIR_PATH); Logger l1 = new Logger(); watcher.addObserver(l1); watcher.execute(); // 创建一系列临时文件 List<String> files = new ArrayList<>(); for(int i=0; i<ADD_TIMES; i++){ files.add(File.createTempFile(PREFIX, SUFFIX, DIR).toString()); } // 延迟等待后台任务的执行 Thread.sleep(4000); watcher.shutdown(); System.out.println("finished"); } }
Console窗口显示的测试内容
test5769907807190550725.txt has been ENTRY_CREATE
test4657672246246330348.txt has been ENTRY_CREATE
test1823102943601166149.txt has been ENTRY_CREATE
finished
test4657672246246330348.txt has been ENTRY_CREATE
test1823102943601166149.txt has been ENTRY_CREATE
finished