Guide sur WatchService dans Java NIO2

1. Vue d’ensemble

Dans cet article, nous allons explorer l’interface WatchService des API de système de fichiers Java NIO.2. C’est l’une des fonctionnalités les moins connues des API IO les plus récentes introduites dans Java 7, à côté de l’interface FileVisitor .

Pour utiliser l’interface WatchService dans vos applications, vous devez importer les classes appropriées:

import java.nio.file.** ;

2. Pourquoi utiliser WatchService

Un exemple courant pour comprendre ce que fait le service est en réalité l’EDI.

Vous avez peut-être remarqué que l’EDI détecte toujours une modification des fichiers de code source qui se produit en dehors de lui-même. Certains IDE vous informent à l’aide d’une boîte de dialogue vous permettant de choisir de recharger le fichier à partir du système de fichiers, d’autres ne font que mettre à jour le fichier en arrière-plan.

De même, les frameworks plus récents tels que Play effectuent également un rechargement à chaud du code de l’application par défaut - chaque fois que vous effectuez des éditions à partir d’un éditeur.

Ces applications utilisent une fonctionnalité appelée file change notification disponible dans tous les systèmes de fichiers.

Fondamentalement, nous pouvons écrire du code pour interroger le système de fichiers pour connaître les modifications apportées à des fichiers et des répertoires spécifiques . Cependant, cette solution n’est pas évolutive, en particulier si les fichiers et les répertoires atteignent des centaines et des milliers.

Dans Java 7 NIO.2, l’API WatchService fournit une solution évolutive permettant de surveiller les modifications apportées aux répertoires. Elle possède une API propre et est tellement bien optimisée pour les performances que nous n’avons pas besoin de mettre en œuvre notre propre solution.

3. Comment fonctionne le WatchService ?

Pour utiliser les fonctionnalités WatchService , la première étape consiste à créer une instance WatchService en utilisant la classe java.nio.file.FileSystems :

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

Ensuite, nous devons créer le chemin du répertoire que nous voulons surveiller:

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

Après cette étape, nous devons enregistrer le chemin avec le service de surveillance. Il y a deux concepts importants à comprendre à ce stade. La classe StandardWatchEventKinds et la classe WatchKey . Jetez un coup d’œil au code d’enregistrement suivant pour comprendre où chaque chute a lieu. Nous suivrons ceci avec des explications du même:

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

Notez seulement deux choses importantes ici: premièrement, l’appel d’API d’enregistrement de chemin prend l’instance de service de surveillance comme premier paramètre, suivie des arguments variables de StandardWatchEventKinds . Deuxièmement, le type de retour du processus d’enregistrement est une instance WatchKey .

3.1. Les StandardWatchEventKinds

Il s’agit d’une classe dont les instances indiquent au service de surveillance les types d’événements à surveiller dans le répertoire enregistré. Il y a actuellement quatre événements possibles à surveiller:

  • StandardWatchEventKinds.ENTRY CREATE __ - déclenché lors d’une nouvelle entrée

est faite dans le répertoire surveillé. Cela peut être dû à la création d’un nouveau fichier ou au changement de nom d’un fichier existant.

  • StandardWatchEventKinds.ENTRY MODIFY __ - déclenché lorsqu’une instance existante

l’entrée dans le répertoire surveillé est modifiée. Tous les fichiers édités déclenchent cet événement. Sur certaines plates-formes, même la modification des attributs de fichier le déclenchera.

  • StandardWatchEventKinds.ENTRY DELETE __ - déclenché lorsqu’une entrée est

supprimé, déplacé ou renommé dans le répertoire surveillé.

  • StandardWatchEventKinds.OVERFLOW - déclenché pour indiquer la perte ou

événements mis au rebut. Nous ne nous concentrerons pas beaucoup dessus

3.2. La WatchKey

Cette classe représente l’enregistrement d’un répertoire avec le service de surveillance. Son instance nous est renvoyée par le service de surveillance lorsque nous enregistrons un répertoire et lorsque nous lui demandons si des événements pour lesquels nous nous sommes enregistrés se sont produits.

Le service de surveillance ne nous propose aucune méthode de rappel qui est appelée chaque fois qu’un événement se produit. Nous ne pouvons l’interroger que de différentes manières pour trouver cette information.

Nous pouvons utiliser l’API poll :

WatchKey watchKey = watchService.poll();

Cet appel d’API revient immédiatement. Il renvoie la clé de surveillance suivante en file d’attente dont l’un des événements s’est produit ou la valeur null si aucun événement enregistré ne s’est produit.

Nous pouvons également utiliser une version surchargée qui prend un argument timeout :

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

Cet appel d’API est similaire au précédent en valeur de retour. Cependant, il bloque pendant timeout les unités de temps pour donner plus de temps pendant lequel un événement peut se produire au lieu de renvoyer la valeur null immédiatement.

Enfin, nous pouvons utiliser l’API take :

WatchKey watchKey = watchService.take();

Cette dernière approche bloque simplement jusqu’à ce qu’un événement se produise.

Nous devons noter quelque chose de très important ici: lorsque l’instance WatchKey est renvoyée par l’une des API poll ou take , elle ne capturera plus d’événements si son API de réinitialisation n’est pas appelée:

watchKey.reset();

Cela signifie que l’instance de clé de surveillance est supprimée de la file d’attente du service de surveillance chaque fois qu’elle est renvoyée par une opération d’interrogation. L’appel d’API reset le remet en file d’attente pour attendre d’autres événements.

L’application la plus pratique du service d’observation nécessiterait une boucle au sein de laquelle nous vérifions en permanence les modifications apportées au répertoire surveillé et procédons en conséquence. Nous pouvons utiliser l’idiome suivant pour implémenter ceci:

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

Nous créons une clé de surveillance pour stocker la valeur de retour de l’opération d’interrogation.

La boucle while restera bloquée jusqu’au retour de l’instruction conditionnelle avec une clé watch ou null.

Lorsque nous obtenons une clé de surveillance, la boucle while exécute le code qu’il contient. Nous utilisons l’API WatchKey.pollEvents pour renvoyer une liste des événements survenus. Nous utilisons ensuite une boucle for each pour les traiter un par un.

Une fois tous les événements traités, nous devons appeler l’API reset pour remettre la clé de surveillance en file d’attente.

4. Exemple d’observation de répertoire

Puisque nous avons couvert l’API WatchService dans la sous-section précédente et son fonctionnement en interne et son utilisation, nous pouvons maintenant aller de l’avant et examiner un exemple complet et pratique.

Pour des raisons de portabilité, nous allons surveiller l’activité dans le répertoire de base de l’utilisateur, qui devrait être disponible sur tous les systèmes d’exploitation modernes.

Le code ne contient que quelques lignes de code, nous allons donc le conserver dans la méthode principale:

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();
        }
    }
}

Et c’est tout ce que nous devons vraiment faire. Vous pouvez maintenant lancer le cours pour commencer à regarder un répertoire.

Lorsque vous accédez au répertoire de base de l’utilisateur et effectuez une opération de manipulation de fichier, telle que la création d’un fichier ou d’un répertoire, la modification du contenu d’un fichier ou même la suppression d’un fichier, tout sera consigné sur la console.

Par exemple, si vous accédez à la page d’accueil de l’utilisateur, cliquez avec le bouton droit de la souris sur l’espace, sélectionnez ` _new → file` pour créer un nouveau fichier, puis nommez-le testFile_ .

Ensuite, vous ajoutez du contenu et enregistrez. La sortie sur la console ressemblera à ceci:

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.

N’hésitez pas à modifier le chemin pour qu’il pointe vers le répertoire que vous souhaitez regarder.

5. Conclusion

Dans cet article, nous avons exploré certaines des fonctionnalités moins couramment utilisées disponibles dans les API de système de fichiers Java 7 NIO.2, notamment l’interface WatchService .

Nous avons également réussi à créer une application de surveillance de répertoires pour en démontrer la fonctionnalité.

Et, comme toujours, le code source complet des exemples utilisés dans cet article est disponible dans le projet Github .