1. 概要

この記事では、JavaNIO.2ファイルシステムAPIのWatchServiceインターフェースについて説明します。 これは、FileVisitorインターフェースとともにJava7で導入された新しいIOAPIのあまり知られていない機能の1つです。

アプリケーションでWatchServiceインターフェースを使用するには、適切なクラスをインポートする必要があります。

import java.nio.file.*;

2. WatchServiceを使用する理由

サービスの機能を理解するための一般的な例は、実際にはIDEです。

IDEは常にそれ自体の外部で発生するソースコードファイルの変更を検出することに気付いたかもしれません。 一部のIDEは、ファイルシステムからファイルをリロードするかどうかを選択できるようにダイアログボックスを使用して通知します。その他のIDEは、バックグラウンドでファイルを更新するだけです。

同様に、Playなどの新しいフレームワークも、任意のエディターから編集を実行しているときはいつでも、デフォルトでアプリケーションコードのホットリロードを実行します。

これらのアプリケーションは、すべてのファイルシステムで利用可能なファイル変更通知と呼ばれる機能を採用しています。

基本的に、特定のファイルとディレクトリの変更についてファイルシステムをポーリングするコードを記述できます。 ただし、このソリューションは、特にファイルとディレクトリが数百、数千に達する場合はスケーラブルではありません。

Java 7 NIO.2では、 WatchService APIは、ディレクトリの変更を監視するためのスケーラブルなソリューションを提供します。 クリーンなAPIを備えており、パフォーマンスが最適化されているため、独自のソリューションを実装する必要はありません。

3. Watchserviceはどのように機能しますか?

WatchService 機能を使用するには、最初のステップは、java.nio.file.FileSystemsクラスを使用してWatchServiceインスタンスを作成することです。

WatchService watchService = FileSystems.getDefault().newWatchService();

次に、監視するディレクトリへのパスを作成する必要があります。

Path path = Paths.get("pathToDir");

このステップの後、パスをウォッチサービスに登録する必要があります。 この段階で理解すべき2つの重要な概念があります。 StandardWatchEventKindsクラスとWatchKeyクラス。 それぞれがどこに落ちるかを理解するために、次の登録コードを見てください。 これに続いて、同じ説明を行います。

WatchKey watchKey = path.register(
  watchService, StandardWatchEventKinds...);

ここで重要なことは2つだけです。最初に、パス登録API呼び出しは、監視サービスインスタンスを最初のパラメーターとして受け取り、その後にStandardWatchEventKindsの変数引数が続きます。 次に、登録プロセスの戻りタイプはWatchKeyインスタンスです。

3.1. The StandardWatchEventKinds

これは、登録されたディレクトリで監視するイベントの種類をインスタンスが監視サービスに通知するクラスです。 現在、注意すべき4つのイベントがあります。

  • StandardWatchEventKinds.ENTRY_CREATE –監視対象ディレクトリに新しいエントリが作成されたときにトリガーされます。 これは、新しいファイルの作成または既存のファイルの名前変更が原因である可能性があります。
  • StandardWatchEventKinds.ENTRY_MODIFY –監視対象ディレクトリの既存のエントリが変更されたときにトリガーされます。 すべてのファイル編集がこのイベントをトリガーします。 一部のプラットフォームでは、ファイル属性を変更してもトリガーされます。
  • StandardWatchEventKinds.ENTRY_DELETE –監視対象ディレクトリでエントリが削除、移動、または名前変更されたときにトリガーされます。
  • StandardWatchEventKinds.OVERFLOW –失われたイベントまたは破棄されたイベントを示すためにトリガーされます。 あまり焦点を当てません

3.2. The WatchKey

このクラスは、監視サービスへのディレクトリの登録を表します。 そのインスタンスは、ディレクトリを登録するとき、および登録したイベントが発生したかどうかを監視サービスに問い合わせるときに、監視サービスによって返されます。

Watchサービスは、イベントが発生するたびに呼び出されるコールバックメソッドを提供しません。 この情報を見つけるには、さまざまな方法でしかポーリングできません。

pollAPIを使用できます。

WatchKey watchKey = watchService.poll();

このAPI呼び出しはすぐに戻ります。 イベントが発生した次のキューに入れられたウォッチキーを返します。登録されたイベントが発生していない場合はnullを返します。

timeout引数を取るオーバーロードバージョンを使用することもできます。

WatchKey watchKey = watchService.poll(long timeout, TimeUnit units);

このAPI呼び出しは、戻り値が前のAPI呼び出しと似ています。 ただし、 timeout 単位の時間ブロックして、すぐにnullを返すのではなく、イベントが発生する可能性のある時間を増やします。

最後に、 takeAPIを使用できます。

WatchKey watchKey = watchService.take();

この最後のアプローチは、イベントが発生するまで単純にブロックします。

ここで非常に重要なことに注意する必要があります。WatchKeyインスタンスがポーリングまたはテイクAPIのいずれかによって返される場合、リセットAPIが呼び出されないと、それ以上のイベントはキャプチャされません:

watchKey.reset();

これは、監視キーインスタンスがポーリング操作によって返されるたびに監視サービスキューから削除されることを意味します。 reset API呼び出しは、それをキューに戻し、さらにイベントを待機します。

ウォッチャーサービスの最も実用的なアプリケーションでは、監視対象ディレクトリの変更を継続的にチェックし、それに応じて処理するループが必要になります。 次のイディオムを使用してこれを実装できます。

WatchKey key;
while ((key = watchService.take()) != null) {
    for (WatchEvent<?> event : key.pollEvents()) {
        //process
    }
    key.reset();
}

ポーリング操作の戻り値を格納するための監視キーを作成します。 whileループは、条件ステートメントが監視キーまたはnullのいずれかで返されるまでブロックされます。

ウォッチキーを取得すると、whileループがその中のコードを実行します。 WatchKey.pollEvents APIを使用して、発生したイベントのリストを返します。 次に、 for each ループを使用して、それらを1つずつ処理します。

すべてのイベントが処理されたら、 reset APIを呼び出して、ウォッチキーを再度キューに入れる必要があります。

4. ディレクトリ監視の例

前のサブセクションでWatchService APIと、それが内部でどのように機能するか、またどのように使用できるかについて説明したので、次に、完全で実用的な例を見てみましょう。

移植性の理由から、ユーザーのホームディレクトリでアクティビティを監視します。このディレクトリは、最新のすべてのオペレーティングシステムで利用できるはずです。

コードには数行のコードしか含まれていないため、mainメソッドにそのまま保持します。

public class DirectoryWatcherExample {

    public static void main(String[] args) {
        WatchService watchService
          = FileSystems.getDefault().newWatchService();

        Path path = Paths.get(System.getProperty("user.home"));

        path.register(
          watchService, 
            StandardWatchEventKinds.ENTRY_CREATE, 
              StandardWatchEventKinds.ENTRY_DELETE, 
                StandardWatchEventKinds.ENTRY_MODIFY);

        WatchKey key;
        while ((key = watchService.take()) != null) {
            for (WatchEvent<?> event : key.pollEvents()) {
                System.out.println(
                  "Event kind:" + event.kind() 
                    + ". File affected: " + event.context() + ".");
            }
            key.reset();
        }
    }
}

そして、それが私たちが本当にしなければならないすべてです。 これで、クラスを実行してディレクトリの監視を開始できます。

ユーザーのホームディレクトリに移動して、ファイルやディレクトリの作成、ファイルの内容の変更、さらにはファイルの削除などのファイル操作アクティビティを実行すると、すべてがコンソールに記録されます。

たとえば、ユーザーのホームに移動した場合、スペースを右クリックし、 ` new –> file` を選択して新しいファイルを作成し、testFileという名前を付けます。 次に、コンテンツを追加して保存します。 コンソールでの出力は次のようになります。

Event kind:ENTRY_CREATE. File affected: New Text Document.txt.
Event kind:ENTRY_DELETE. File affected: New Text Document.txt.
Event kind:ENTRY_CREATE. File affected: testFile.txt.
Event kind:ENTRY_MODIFY. File affected: testFile.txt.
Event kind:ENTRY_MODIFY. File affected: testFile.txt.

見たいディレクトリを指すようにパスを自由に編集してください。

5. 結論

この記事では、Java 7 NIO.2で使用できるあまり一般的ではない機能のいくつか(ファイルシステムAPI、特に WatchService インターフェース)について説明しました。

また、機能を実証するために、ディレクトリ監視アプリケーションを構築する手順を実行することもできました。

また、いつものように、この記事で使用されている例の完全なソースコードは、Githubプロジェクトで入手できます。