CodeGym /Cursos /JAVA 25 SELF /Monitoramento de alterações no sistema de arquivos: Watch...

Monitoramento de alterações no sistema de arquivos: WatchService

JAVA 25 SELF
Nível 40 , Lição 4
Disponível

1. Introdução

WatchService é parte do Java NIO (New I/O) que permite acompanhar alterações no sistema de arquivos em tempo real. Você pode imaginá-lo como um sistema de alarme para pastas: se alguém adicionar, excluir ou modificar um arquivo — você recebe a notificação imediatamente. Esse recurso apareceu no Java 7 junto com o NIO.2; antes disso, os desenvolvedores precisavam fazer polling manual da pasta (polling) ou usar bibliotecas de terceiros.

As aplicações práticas do WatchService são variadas: ele ajuda a processar automaticamente novos arquivos, manter logs e fazer backups, sincronizar pastas com um servidor ou nuvem, além de acompanhar alterações em arquivos de configuração.

Registro de diretórios para monitoramento

Para começar a monitorar alterações, é preciso:

  1. Obter uma instância de WatchService.
  2. Registrar a pasta desejada e indicar quais eventos nos interessam.

Obtendo o WatchService

import java.nio.file.*;

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

Registrando a pasta

Para registrar, usamos o método register do objeto Path:

Path dir = Paths.get("data/uploads");
dir.register(
    watchService,
    StandardWatchEventKinds.ENTRY_CREATE,   // criação de arquivos/pastas
    StandardWatchEventKinds.ENTRY_DELETE,   // exclusão de arquivos/pastas
    StandardWatchEventKinds.ENTRY_MODIFY    // modificação de arquivos/pastas
);

Explicação:

  • ENTRY_CREATE — alguém adicionou algo.
  • ENTRY_DELETE — alguém excluiu algo.
  • ENTRY_MODIFY — alguém modificou um arquivo (por exemplo, adicionou texto).

Importante! WatchService monitora apenas uma pasta por vez (sem subpastas). Se quiser acompanhar toda a hierarquia — é preciso registrar cada subpasta separadamente.

2. Processamento de eventos: loop de espera

Agora que configuramos o monitoramento (mais como um “vizinho observador”, e não no espírito do “Grande Irmão”), podemos aguardar eventos. O WatchService implementa o padrão de “fila de eventos”: assim que algo acontece — o evento é colocado na fila.

Loop principal

while (true) {
    // Aguardamos o surgimento de eventos (chamada bloqueante)
    WatchKey key = watchService.take();

    for (WatchEvent<?> event : key.pollEvents()) {
        // Tipo de evento: criação, exclusão, modificação
        WatchEvent.Kind<?> kind = event.kind();

        // Nome do arquivo/pasta (Path relativo à pasta monitorada)
        Path filename = (Path) event.context();

        if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
            System.out.println("Arquivo/pasta criado(a): " + filename);
        } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
            System.out.println("Arquivo/pasta excluído(a): " + filename);
        } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
            System.out.println("Arquivo/pasta modificado(a): " + filename);
        }
    }

    // É obrigatório resetar a chave; caso contrário, o monitoramento será interrompido!
    boolean valid = key.reset();
    if (!valid) {
        break; // Pasta indisponível; saindo
    }
}

Como isso funciona?

  • WatchService.take() — bloqueia a thread até que surja um evento (você pode usar poll() para modo não bloqueante).
  • key.pollEvents() — lista de todos os eventos acumulados.
  • event.context() — nome do arquivo ou pasta alterado (relativo ao diretório monitorado).
  • Após processar os eventos, chame obrigatoriamente key.reset(). Se a pasta foi excluída ou ficou indisponível, reset() retorna false — você pode encerrar o loop.

Exemplo completo: monitorando a pasta "data/uploads"

Vamos adicionar ao nosso aplicativo de estudo um simples “alarme” para a pasta de uploads:

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("Monitorando a pasta " + dir.toAbsolutePath());

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

            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("Pasta indisponível; monitoramento encerrado.");
                break;
            }
        }
    }
}

Experimente: Execute esse código e tente criar, excluir ou modificar um arquivo na pasta "data/uploads". O programa reagirá na hora!

3. Limitações e particularidades do WatchService

Apenas uma pasta, sem subpastas

WatchService monitora apenas a pasta que você registrou. Se ela tiver subpastas, as alterações dentro delas não serão percebidas — é preciso registrar cada subpasta separadamente.

O que fazer?
Se você deseja monitorar toda a hierarquia, será necessário percorrer todas as subpastas e registrá-las uma a uma. Por exemplo, ao criar uma nova subpasta — registre-a imediatamente.

Particularidades em diferentes sistemas operacionais

Windows: WatchService funciona de forma bastante estável, mas às vezes pode “agrupar” vários eventos em um só (por exemplo, ao copiar um arquivo grande).

Linux/macOS: A implementação é baseada em mecanismos do sistema (inotify, kqueue). Às vezes os eventos podem chegar com atraso ou, ao contrário, em excesso (por exemplo, ENTRY_MODIFY a cada salvamento).

Apenas eventos com o nome do alvo

WatchService informa apenas o nome do objeto alterado (relativo à pasta monitorada), mas não fornece informações detalhadas sobre o que mudou dentro do arquivo. Se for necessário saber o que exatamente mudou — leia o arquivo manualmente.

Perda de eventos sob alta carga

Se na pasta ocorrerem muitas alterações em um curto período (por exemplo, cópia em massa de milhares de arquivos), a fila de eventos pode encher e parte dos eventos será perdida. Para tarefas críticas, convém implementar verificações adicionais.

4. Exemplos práticos

Processamento automático de novos arquivos

Suponha que você esteja desenvolvendo um programa que deve processar automaticamente novas imagens que aparecem na pasta "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("Nova foto: " + filename);
                // Aqui você pode adicionar processamento: cópia, compactação, análise etc.
            }
        }
    }
    key.reset();
}

Implementação de um logger simples de alterações

Você pode salvar todos os eventos em um arquivo de log separado:

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

Acompanhando a criação de novas subpastas (e seu registro)

Se uma nova subpasta for criada dentro da pasta monitorada, você pode registrá-la imediatamente para monitoramento contínuo:

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("Monitoramento iniciado para a nova subpasta: " + createdPath);
    }
}

5. Pontos importantes e erros comuns

Erro nº 1: esqueceu de chamar key.reset(). Se você não resetar a chave após processar os eventos, o monitoramento da pasta será interrompido e nenhum outro evento será recebido. Esse é o clássico “tropeço” para iniciantes: parece que tudo está funcionando e, de repente — pronto! — o programa fica silencioso.

Erro nº 2: ignorar exceções. Trabalhar com o sistema de arquivos sempre pode trazer surpresas: a pasta pode ser excluída, o disco — desconectado, as permissões — alteradas. Se você não tratar exceções (IOException, ClosedWatchServiceException), o programa pode encerrar com falha.

Erro nº 3: monitorar apenas uma pasta. Muitos esperam que, ao registrar uma pasta, todas as subpastas também serão monitoradas. Não é assim! Se for necessário acompanhar toda a estrutura — implemente o registro recursivo.

Erro nº 4: bloquear a thread principal. WatchService.take() bloqueia a thread até aparecer um evento. Se a thread principal do programa precisar fazer algo mais, execute o monitoramento em uma thread separada.

Erro nº 5: perda de eventos sob alta carga. Se ocorrerem muitas mudanças na pasta, a fila de eventos pode lotar. Para aplicativos críticos, é recomendável implementar uma verificação periódica do estado da pasta (por exemplo, comparar a lista de arquivos uma vez por minuto).

Erro nº 6: tratamento incorreto de caminhos relativos. event.context() retorna o nome do arquivo relativo à pasta monitorada. Se você precisar do caminho absoluto — use dir.resolve((Path) event.context()).

1
Pesquisa/teste
Operações com diretórios, nível 40, lição 4
Indisponível
Operações com diretórios
Operações com arquivos e diretórios
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION