Advanced Java Services | WatchService |
Eine der interessantesten Neuerungen in der Version 7 ist der WatchService. Mit ihm kann man die Vorgänge in einem Verzeichnis dokumentieren. Der WatchService übernimmt direkt die Eventaufrufe des Betriebssystems und ist damit vergleichbar mit einem LowLevelEvent. Aus diesem Grund erfordert das Einrichten eines WatchService einen gewissen Aufwand. Im folgenden wird das Vorgehen beschrieben.
Ein WatchService dient im Normalfall dazu, die Vorgänge in einem Verzeichnis zu überwachen. Das Verzeichnis liegt in einem bnestimmten FileSystem. Man muß sich also zuerst klarmachen in welchem Dateisystem das zu beobachtende Directory liegt. In den meisten Fällen ist es wohl das lokale FileSystem bzw. das DefaultFileSystem. Das Vorgehen ist aber für jedes FileSystem das gleiche.
FileSystem fileSystem = FileSystems.getDefault(); // liefert c:\, d:\, usw. auf Windowssystemen
Welche Rootverzeichnisse zu einem FileSystem gehören, liefert die Methode getRootDirectories().
Iterable<Path> iter = fileSystem.getRootDirectories();
WatchService watchService = fileSystem.newWatchService();
Als nächstes braucht man ein ein Pathobjekt in diesem FileSystem das auf das zu überwachende Verzeichnis zeigt.
Path dirToWatch = Paths.get("c://tmp");
Das Interface Path stellt zwei register-Methoden bereit.
Modifier and Type | Method and Description |
WatchKey | register(WatchService watcher, WatchEvent.Kind<?>... events)
Registers the file located by this path with a watch service. |
WatchKey | register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)
Registers the file located by this path with a watch service. |
Die zu überwachenden Ereignisse sind als Felder in der KLasse StandardWatchEventKinds angelegt. Die vier statischen Konstanten sind vom Typ WatchEvent.Kind<T>. WatchEvent.Kind<T> selbst ist ein statisches inneres Interface von WatchEvent<T>.
OVERFLOW ist ein spezieller Event, der signalisiert, daß Events verlorengegangen oder verworfen worden sind. Die restlichen Eventnamen sind selbsterklärend. Events der Form ENTRY_CREATE und ENTRY_DELETE treten immer genau einmal auf, die restlichen Events können für einen Vorgang auch öfter als einmal auftreten.
WatchKey watchKey = dirToWatch.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey verwaltet die anfallenden Events. Beim Registrieren wird er meist noch nicht gebraucht.
Da der WatchService mit Events arbeitet, braucht man einen Eventhandler. Das Einrichten dieses Handler erfordert etwas mehr Aufwand. Die Events werden von einem Watchkey verwaltet, den der WatchService zur Verfügung stellt. Der Eventhandler muß also ein Watchserviceobjekt erhalten. Am besten läuft er als eigener Thread. Der Aufruf kann etwa folgendermaßen aus:
WatchHandler wh = new WatchHandler(watchService, dirToWatch); wh.start();
Die folgende Klasse WatchHandler ist eine Beispielimplementierung.
package hms; import java.util.List; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; public class WatchHandler extends Thread { WatchService watchService; Path dirToWatch; public WatchHandler(WatchService watchService, Path dirToWatch) { this.watchService = watchService; this.dirToWatch = dirToWatch; } /** */ @Override public void run() { System.out.println("run"); for(;;) { try { WatchKey key = watchService.take(); // blockiert List<WatchEvent<?>> eventList = key.pollEvents(); System.out.println("size = " + eventList.size()); for(WatchEvent<?> e : eventList) { System.out.print(e.kind() + " -> "); Path name = (Path)e.context(); //System.out.print(name.getParent()); // context liefert nur den Dateinamen, parent ist null ! Path path = dirToWatch.resolve(name); System.out.print(path); if (Files.isDirectory(path)) System.out.println(" <dir>"); else System.out.println(" <file<"); } boolean valid = key.reset(); if (!valid) { break; } } catch(InterruptedException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } } // end for } // end run } // end class
Ausgangspunkt ist das Statement watchService.take(). take blockiert bis Events anliegen und liefert dann ein WatchKey-Objekt. Dieses Objekt enthält eine Liste von Events vom Typ WatchEvent, die wir mit der Methode pollEvents() bekommen. Diese Liste wird dann in einer for-Schleife untersucht. Es kann sein, daß man einen ungültigen WatchKey erhält. In diesem Fall wird die Endlosschleife beendet.
Hier noch die vollständige main()-Klasse, die den WatchService einrichtet und den Thread startet.
public class WatchServiceDemo { public static void main(String[] args) { FileSystem fs = FileSystems.getDefault(); // sun.nio.fs.WindowsFileSystem try { Path dirToWatch = Paths.get("c://tmp"); WatchService watchService = fs.newWatchService(); // sun.nio.fs.WindowsWatchService WatchKey watchKey = dirToWatch.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); WatchHandler wh = new WatchHandler(watchService, dirToWatch); wh.start(); } catch(IOException ex) { ex.printStackTrace(); } } } // end class WatchServiceDemo