2025-05-17

 

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.*;

 

/**

 * 監控一個目錄內文件的更新、創建和刪除事件(不包括子目錄)

 *

 * 對於https://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");

    }

}

 

 

 Console窗口顯示的測試內容

 

 

 

test5769907807190550725.txt has been ENTRY_CREATE

test4657672246246330348.txt has been ENTRY_CREATE

test1823102943601166149.txt has been ENTRY_CREATE

finished

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *