《模式——工程化实现及扩展》(设计模式Java 版)
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;}
}
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));
}
}
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
marvellousworks.practicalpattern.concept.unittest;
import static org.junit.Assert. * ;
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.Test;
import static java.nio.file.StandardWatchEventKinds. * ;
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());
assertTrue(args.getFileName().startsWith(PREFIX));
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 " );
}
}
import static org.junit.Assert. * ;
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.Test;
import static java.nio.file.StandardWatchEventKinds. * ;
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());
assertTrue(args.getFileName().startsWith(PREFIX));
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