2025. 7. 31. 16:51ㆍJava
WatchService❔
WatchService는 Java NIO에서 제공하는 파일 시스템 감시 기능입니다.
디렉터리 내 파일이나 서브 디렉터리의 변경사항을 감지할 수 있습니다.
✅ 변경 사항: 생성, 수정, 삭제 등
Java 7에서 처음 도입되었으며 운영체제의 이벤트 기반 감시 기능을 활용하여
성능도 좋고, 실시간에 가까운 처리가 가능합니다.
NIO
New Input/Out
I/O보다 강력한 기능을 제공하는 비동기 버퍼 기반의 고성능 I/O 처리 모델
java.io 패키지가 스트림 기반이라면 NIO는 버퍼 기반이고, 논블로킹 IO, 채널 기반 구조 제공
핵심 구성 요소
1. WatchService
- 지정한 디렉토리의 변경 이벤트를 감시하는 Java NIO의 서비스입니다.
- 감지된 이벤트는 내부 큐에 저장되며, take() 또는 poll() 메서드를 통해 하나씩 꺼내어 처리할 수 있습니다.
- 감시할 이벤트의 종류는 ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY 등이 있으며,
WatchKey를 통해 등록합니다.
2. WatchKey
- 특정 디렉토리에 대한 감시 등록 정보를 나타냅니다.
- 디렉터리에서 이벤트가 발생하면, 해당 이벤트들을 포함한 WatchKey가 WatchService로 반환됩니다.
- 이벤트 처리 후 reset() 을 호출해야 감시 상태가 유지됩니다. ⭐⭐
3. WatchEvent<T>
- 실제로 발생한 파일/디렉터리 변경 이벤트를 나타내는 객체입니다.
- 주요 속성
- kind(): 이벤트 종류(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)
- context(): 이벤트가 발생한 상대 경로
- count(): 이벤트가 몇 번 중복되어 발생했는지 나타내는 값
4. WatchEvent.Kind<T>
- 이벤트 종류를 나타냅니다.
- WatchService가 감지한 이벤트가 어떤 유형인지 구분할 때 사용됩니다.
| 이벤트 종류 | 설명 |
| ENTRY_CREATE | 파일 또는 디렉터리 생성 |
| ENTRY_DELETE | 파일 또는 디렉터리 삭제 |
| ENTRY_MODIFY | 파일 내용 변경 |
기본 동작 흐름
1단계: WatchService 생성
WatchService watchService = FileSystems.getDefault().newWatchService();
- WatchService 인스턴스를 생성합니다.
- 파일 시스템에서 디렉터리의 변경 이벤트를 감지할 수 있도록 설정합니다.
- FileSystems.getDefault() 는 현재 사용 중인 기본 파일 시스템을 반환하며, newWatchService() 를 통해
감시 서비스 객체를 얻습니다.
2단계: 감시 대상 디렉터리 등록
Path path = Paths.get("경로");
path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
- 감시할 디렉토리 경로를 Path 객체로 지정합니다.
- register() 메서드를 통해 어떤 이벤트를 감지할 것인지 등록합니다.
- 등록된 디렉터리에 변화가 생기면 이벤트가 WatchService에 전달됩니다.
3단계: 이벤트 루프 처리
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path fileName = (Path) event.context();
// 파일 처리 로직
}
boolean valid = key.reset(); // watchKey 재설정 ➡️ 계속 감시
if (!valid) break; // 디렉토리가 삭제되거나 접근 불가하면 종료
}
- watchService.take()는 이벤트가 발생할 때까지 대기합니다. ➡️ Blocking 방식
- 이벤트가 발생하면 WatchKey를 통해 이벤트 목록을 가져옵니다.
- 각 이벤트를 순회하면서 어떤 종류의 이벤트인지 어떤 파일 또는 디렉터리에서 발생했는지를 확인할 수 있습니다.
※ 상단에 언급한 kind, context, count
한계와 제약
WatchService는 파일 시스템 이벤트를 감지하는 데 유용하지만, 모든 상황에 적합한 것은 아닙니다.
1. 디렉토리 단위 감시만 가능
WatchService는 파일 단위가 아닌 디렉토리 단위로 감시합니다.
대상 디렉터리의 하위 디렉터리는 자동으로 감지되지 않고, 하위 디렉터리까지 감시하려면 직접 재귀적으로 등록해야 합니다.
ex) /home/data: 대상 디렉터리, /home/data/sub 하위 디렉터리의 이벤트는 감지되지 않습니다.
2. 이벤트 유실 가능성
파일 생성, 수정, 삭제 이벤트가 빠르게 발생할 경우 내부 큐(WatchKey)가 가득 차면서 이벤트가 유실될 수 있습니다.
3. 이벤트 중복 발생
파일을 저장하거나 이동할 때 ENTRY_CREATE와 ENTRY_MODIFY가 동시에 발생하는 등 중복 이벤트가 발생할 수 있습니다.
따라서, 이벤트를 구분하고 처리하는 로직이 필요합니다.
4. 정확한 타이밍 보장 어려움
이벤트가 발생한 즉시 감지되는 것이 아니라 시스템의 디스크 감시(polling) 주기에 따라 약간의 지연이 생길 수 있습니다.
5. 플랫폼 의존성
OS별로 WatchServiced의 동작 방식이나 지원 수준이 다릅니다.
일부 OS에서는 특정 이벤트를 정확하게 감지하지 못할 수 있습니다.
6. 다중 WatchService 사용 시 리소스 문제
다수의 디렉터리를 동시에 감시하거나 여러 WatchService 인스턴스를 생성할 경우 자원 사용량이 급증합니다.
현재 WatchSerivce 적용 사례
🎥 RTSP 영상 녹화 감지 + 영상 처리 자동화
현재 진행 중인 프로젝트에서 실시간으로 전송되는 RTSP 영상 스트림을 FFmpeg를 이용해
1분 단위로 녹화 파일을 저장하고 있습니다.
저장된 파일은 WatchSerivce를 통해 자동으로 감지되며
감지된 파일 명을 변경하고, 영상의 메타 정보를 DB에 저장하는 데 활용하고 있습니다.
사례 예제 코드
저의 프로젝트에서 사용한 코드 일부분입니다.
WatchService를 활용하여 특정 디렉토리 내 파일 생성 이벤트를 감지하는 로직을 구성했습니다.
public void watchDirectory(String id) {
WatchService watchService = null;
try {
// 디렉토리 존재 여부 확인
String pathStr = getPath(vehicleId);
Path path = createDirectoryIfNotExists(pathStr);
// WatchService 생성
watchService = FileSystems.getDefault().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE); // 생성 이벤트
// 이벤트가 발생할 때까지 대기
while (true) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException e) {
log.error("[watchDirectory] InterruptedException > 에러 메시지 === {}", e.getMessage());
Thread.currentThread().interrupt();
break;
}
// 감지된 이벤트 처리
for (WatchEvent<?> event : key.pollEvents()) {
Path detectedFileName = (Path) event.context();
handleDetectedFile(detectedFileName);
}
// WatchKey 초기화
Boolean valid = key.reset();
if (!valid) {
log.info("[watchDirectory] WatchKey is no longer valid. Stopping watch service.");
break;
}
}
} catch (IOException e) {
log.error("[watchDirectory] IOException > 에러 메시지 === {}", e.getMessage());
} finally {
try {
watchService.close();
} catch (IOException e) {
log.error("[watchDirectory] [ {} ] Error closing WatchService.", id);
}
}
}