CodeGym /Cours /JAVA 25 SELF /Surveillance des modifications du système de fichiers : W...

Surveillance des modifications du système de fichiers : WatchService

JAVA 25 SELF
Niveau 40, Leçon 4
Disponible

1. Introduction

WatchService est une partie de Java NIO (New IO) qui permet de suivre en temps réel les modifications du système de fichiers. On peut l’imaginer comme une alarme pour des dossiers : dès que quelqu’un ajoute, supprime ou modifie un fichier, vous recevez immédiatement une notification. Cette fonctionnalité est apparue avec Java 7 en même temps que NIO.2, et auparavant les développeurs devaient soit interroger le dossier manuellement (polling), soit utiliser des bibliothèques tierces.

Les usages pratiques de WatchService sont variés : il aide à traiter automatiquement les nouveaux fichiers, à tenir des journaux et faire des sauvegardes, à synchroniser des dossiers avec un serveur ou le cloud, ainsi qu’à surveiller les modifications dans des fichiers de configuration.

Enregistrement des dossiers à surveiller

Pour commencer à surveiller les modifications, il faut :

  1. Obtenir une instance de WatchService.
  2. Enregistrer le dossier souhaité et indiquer les événements qui nous intéressent.

Obtenir WatchService

import java.nio.file.*;

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

Enregistrer un dossier

Pour l’enregistrement, on utilise la méthode register de l’objet Path :

Path dir = Paths.get("data/uploads");
dir.register(
    watchService,
    StandardWatchEventKinds.ENTRY_CREATE,   // création de fichiers/dossiers
    StandardWatchEventKinds.ENTRY_DELETE,   // suppression de fichiers/dossiers
    StandardWatchEventKinds.ENTRY_MODIFY    // modification de fichiers/dossiers
);

Explications :

  • ENTRY_CREATE — quelque chose a été ajouté.
  • ENTRY_DELETE — quelque chose a été supprimé.
  • ENTRY_MODIFY — un fichier a été modifié (par exemple, du texte ajouté).

Important ! WatchService ne surveille qu’un seul dossier à la fois (sans sous-dossiers). Si vous souhaitez suivre toute l’arborescence — vous devez enregistrer chaque sous-dossier séparément.

2. Traitement des événements : boucle d’attente

Maintenant que nous avons configuré la surveillance (plutôt comme un « voisin observateur » que dans l’esprit du « Big Brother »), on peut attendre les événements. WatchService met en œuvre le pattern « file d’événements » : dès que quelque chose se produit — l’événement est placé dans la file.

Boucle principale

while (true) {
    // En attente d'événements (appel bloquant)
    WatchKey key = watchService.take();

    for (WatchEvent<?> event : key.pollEvents()) {
        // Type d’événement : création, suppression, modification
        WatchEvent.Kind<?> kind = event.kind();

        // Nom du fichier/dossier (Path, relatif au dossier surveillé)
        Path filename = (Path) event.context();

        if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
            System.out.println("Fichier/dossier créé : " + filename);
        } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
            System.out.println("Fichier/dossier supprimé : " + filename);
        } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
            System.out.println("Fichier/dossier modifié : " + filename);
        }
    }

    // Toujours réinitialiser la clé, sinon la surveillance s'arrêtera !
    boolean valid = key.reset();
    if (!valid) {
        break; // Dossier indisponible, on sort
    }
}

Comment cela fonctionne-t-il ?

  • WatchService.take() — bloque le thread jusqu’à l’arrivée d’un événement (on peut utiliser poll() pour un mode non bloquant).
  • key.pollEvents() — la liste de tous les événements accumulés.
  • event.context() — le nom du fichier ou du dossier modifié (relatif au dossier surveillé).
  • Après avoir traité les événements, appelez impérativement key.reset(). Si le dossier a été supprimé ou est devenu indisponible, reset() renvoie false — vous pouvez terminer la boucle.

Exemple complet : nous surveillons le dossier "data/uploads"

Ajoutons à notre application d’apprentissage une simple « alarme » sur le dossier de téléchargements :

import java.nio.file.*;
import java.io.IOException;

public class WatcherDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Path dir = Paths.get("data/uploads");
        if (!Files.exists(dir)) {
            Files.createDirectories(dir);
        }

        WatchService watchService = FileSystems.getDefault().newWatchService();
        dir.register(
            watchService,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.ENTRY_MODIFY
        );

        System.out.println("Surveillance du dossier " + dir.toAbsolutePath());

        while (true) {
            WatchKey key = watchService.take(); // on attend les événements

            for (WatchEvent<?> event : key.pollEvents()) {
                WatchEvent.Kind<?> kind = event.kind();
                Path filename = (Path) event.context();
                System.out.printf("[%s] %s\n", kind.name(), filename);
            }

            boolean valid = key.reset();
            if (!valid) {
                System.out.println("Dossier indisponible, surveillance terminée.");
                break;
            }
        }
    }
}

À essayer : Exécutez ce code et essayez de créer, supprimer ou modifier un fichier dans le dossier "data/uploads". Le programme réagira immédiatement !

3. Limitations et particularités WatchService

Un seul dossier, sans sous-dossiers

WatchService ne surveille que le dossier que vous avez enregistré. S’il contient des sous-dossiers, les modifications à l’intérieur de ceux-ci ne seront pas détectées — vous devez enregistrer chaque sous-dossier séparément.

Que faire ?
Si vous souhaitez surveiller toute l’arborescence, il faudra parcourir tous les sous-dossiers et les enregistrer un par un. Par exemple, lors de la création d’un nouveau sous-dossier — l’enregistrer immédiatement.

Particularités selon les OS

Windows : WatchService fonctionne de manière assez stable, mais peut parfois « fusionner » plusieurs événements en un seul (par exemple lors de la copie d’un gros fichier).

Linux/macOS : L’implémentation s’appuie sur des mécanismes système (inotify, kqueue). Parfois, les événements peuvent arriver avec un délai, ou au contraire en trop grand nombre (par exemple, ENTRY_MODIFY à chaque enregistrement).

Uniquement des événements par nom

WatchService ne fournit que le nom de l’élément modifié (relatif au dossier surveillé), mais pas le détail de ce qui a changé à l’intérieur du fichier. Si vous devez savoir ce qui a changé — lisez le fichier manuellement.

Perte d’événements en cas de surcharge

Si trop de modifications surviennent dans le dossier en peu de temps (par exemple, la copie massive de milliers de fichiers), la file d’événements peut déborder et une partie des événements sera perdue. Pour les tâches critiques, utilisez des vérifications supplémentaires.

4. Exemples pratiques

Traitement automatique des nouveaux fichiers

Supposons que vous écrivez un programme qui doit traiter automatiquement les nouvelles images apparaissant dans le dossier "photos/incoming".

Path dir = Paths.get("photos/incoming");
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);

while (true) {
    WatchKey key = watchService.take();

    for (WatchEvent<?> event : key.pollEvents()) {
        if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            Path filename = (Path) event.context();
            if (filename.toString().endsWith(".jpg")) {
                System.out.println("Nouvelle photo : " + filename);
                // Ici, vous pouvez ajouter un traitement : copie, compression, analyse, etc.
            }
        }
    }
    key.reset();
}

Implémentation d’un logger simple des modifications

On peut enregistrer tous les événements dans un fichier journal séparé :

import java.nio.file.*;
import java.io.*;
import java.time.LocalDateTime;

public class SimpleLogger {
    public static void main(String[] args) throws IOException, InterruptedException {
        Path dir = Paths.get("logs/monitored");
        Files.createDirectories(dir);

        Path logFile = Paths.get("logs/changes.log");
        try (BufferedWriter writer = Files.newBufferedWriter(logFile, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
            WatchService watchService = FileSystems.getDefault().newWatchService();
            dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);

            System.out.println("Surveillance de " + dir);

            while (true) {
                WatchKey key = watchService.take();

                for (WatchEvent<?> event : key.pollEvents()) {
                    String log = String.format("%s [%s] %s\n",
                        LocalDateTime.now(), event.kind().name(), event.context());
                    writer.write(log);
                    writer.flush();
                    System.out.print(log);
                }
                key.reset();
            }
        }
    }
}

Surveillance de la création de nouveaux sous-dossiers (et leur enregistrement)

Si un nouveau sous-dossier est créé dans le dossier surveillé, on peut l’enregistrer immédiatement pour la surveillance future :

if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
    Path createdPath = dir.resolve((Path) event.context());
    if (Files.isDirectory(createdPath)) {
        createdPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
                             StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
        System.out.println("Surveillance démarrée pour le nouveau sous-dossier : " + createdPath);
    }
}

5. Points importants et erreurs courantes

Erreur n° 1 : vous avez oublié d’appeler key.reset(). Si vous ne réinitialisez pas la clé après le traitement des événements, la surveillance du dossier s’arrêtera et vous ne recevrez plus aucun événement. C’est le piège classique pour les débutants : tout semble fonctionner, puis — paf ! — le programme se tait.

Erreur n° 2 : ignorer les exceptions. Travailler avec le système de fichiers est toujours sujet aux imprévus : le dossier peut être supprimé, le disque — déconnecté, les droits — modifiés. Si vous n’interceptez pas les exceptions (IOException, ClosedWatchServiceException), le programme peut se terminer brutalement.

Erreur n° 3 : ne surveiller qu’un seul dossier. Beaucoup s’attendent à ce qu’en enregistrant un dossier, tous les sous-dossiers soient également surveillés. Ce n’est pas le cas ! Si vous devez surveiller toute la structure — implémentez un enregistrement récursif.

Erreur n° 4 : blocage du thread principal. WatchService.take() bloque le thread jusqu’à l’arrivée d’un événement. Si le thread principal du programme doit faire autre chose, lancez la surveillance dans un thread séparé.

Erreur n° 5 : perte d’événements sous forte charge. Si trop de modifications surviennent dans le dossier, la file d’événements peut déborder. Pour les applications critiques, mettez en place une vérification périodique de l’état du dossier (par exemple, comparer la liste des fichiers chaque minute).

Erreur n° 6 : mauvaise gestion des chemins relatifs. event.context() renvoie le nom du fichier relatif au dossier surveillé. Si vous avez besoin d’un chemin absolu — utilisez dir.resolve((Path) event.context()).

1
Mission
JAVA 25 SELF, niveau 40, leçon 4
Bloqué
Installation d'un observateur vigilant pour un nouveau dossier 👁️‍🗨️
Installation d'un observateur vigilant pour un nouveau dossier 👁️‍🗨️
1
Mission
JAVA 25 SELF, niveau 40, leçon 4
Bloqué
Surveillance des modifications et suppressions dans le répertoire clé 🚨
Surveillance des modifications et suppressions dans le répertoire clé 🚨
1
Étude/Quiz
Opérations avec des répertoires, niveau 40, leçon 4
Indisponible
Opérations avec des répertoires
Opérations avec des fichiers et des répertoires
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION