WatchServiceを利用したIOイベントハンドリング

Java7ではWatchServiceを利用して特定のディレクトリ内のイベントを監視するプログラムを簡単に記述することが可能です。

用途としては常駐プロセス系のプログラムで特定ディレクトリのファイル作成、更新、削除などのイベントをハンドリングし任意の処理を行うなど。
(たとえば設定ファイルを監視しておいて、更新されたらその内容を読み取り反映を行う、もしくは特定のファイルが作成されたことをトリガにして任意の処理を開始するといったことなど。)

実際の利用方法はFileSystemクラスのnewWatchService()メソッドよりWatchServiceを取得し、捕捉したいイベント(WatchEvent)とWatchServiceオブジェクトを監視対象となるディレクトリのPathオブジェクトに登録します。

その後は、どういったアプリケーションかにもよりますが、例えば無限ループなどでWatchServiceのtakeメソッドを呼び出し、イベントがキューイングされるまで待機し、イベント発生後takeメソッドの戻り値を利用して特定の処理を行うといったところかと思います。

【サンプルプログラム】

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class Main {

    private static volatile boolean stop = false;

    public static void main(String[] args) {
        FileSystem fileSystem = FileSystems.getDefault();
        // 監視対象ディレクトリのPathを
        Path path = fileSystem.getPath("path_to_dir");
        try {
            // WatchServiceの取得
            WatchService watchService = fileSystem.newWatchService();
            WatchEvent.Kind<?>[] events = {
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_MODIFY,
                    StandardWatchEventKinds.ENTRY_DELETE
            };
            // 捕捉したいイベント種別とWatchServiceをPathに登録
            path.register(watchService, events);
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    stop = false;
                }
            });
            while (!Thread.currentThread().isInterrupted() && !stop) {
                System.out.println("Watching...");
                try {
                    // イベントが発生するまで待機
                    WatchKey watchKey = watchService.take();
                    String watchableName = watchKey.watchable().toString();
                    System.out.println("Watchable : " + watchableName);
                    
                    if (watchKey.isValid()) {
                        for (WatchEvent<?> event : watchKey.pollEvents()) {
                            System.out.println("イベント種別 : " + event.kind());
                            System.out.println("対象コンテンツ : " + event.context());
                            System.out.println("イベント回数 : " + event.count());
                            Path targetPath = FileSystems.getDefault().getPath(watchableName + "\\" + event.context());
                            processContent(targetPath, event);
                            System.out.println();
                        }
                        if (!watchKey.reset()) {
                            // Is this right ?
                            System.out.println("The watch key might be invalid.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("See you.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static final void processContent(final Path targetPath, final WatchEvent<?> watchEvent) {
        // コンテンツがファイルかつ更新イベントの場合コンテンツ内容を出力
        if (Files.isRegularFile(targetPath)) {
            if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                try {
                    if (Files.size(targetPath) > 0) {
                        try(BufferedReader reader = Files.newBufferedReader(targetPath, Charset.defaultCharset())) {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                System.out.println(line);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

上記サンプルはループ内でイベントが発生するまで待機し、ファイル作成、削除の場合はその旨イベントが発生したことを表示し、更新された場合は更新内容も出力します。

サンプルプログラムなのでかなり適当に作成していますが、挙動を確かめるには十分かと。。。