1. 개요
2024년에 들어서면서 새로운 프로젝트를 진행하게되었다
전체적인 프로젝트 내용은 모니터링 시스템이다
log 파일에 에러 메세지(log) 가 발생 하는지 모니터링을 하고
발생하면 관리자에게 알림(mail) 을 해주는 형태이다
기존에 CDC 를 이용해서 queue 데이터를 실시간으로 체크했었고
이번에는 로그파일을 체크해서 알림하는 컴포넌트로 보낼것이다
이번 프로젝트에서 filetracker 기능을 담당하게 되어
해당 게시글에서 구현을 위한 인터페이스와 어떠한 원리인지
하나씩 정리할 것이며 빠른 조사와 테스트를 위해
단일 파일을 처리하는것으로 결정했다
단일 파일을 처리하는것이 정상적으로 작동하면
복수 파일을 처리하는 로직으로 리팩토링할것이며
리팩토링 게시글은 추가로 작성하도록 하겠다
2. Path 인터페이스
Path는 IO의 java.io.File 클래스에 대응되는 NIO 인터페이스이다
NIO의 API에서 파일의 경로를 지정하기 위해 Path를 사용하므로 Path의 사용법을 알아야한다
Path 구현 객체는 java.nio.file.Paths 클래스의 get() 정적 메서드를 호출하여 얻는다
Path path = Paths.get(String first, String....more)
Path path = Paths.get(URI uri);
get()의 매개값은 파일의 경로이다. 문자열로 지정할 수도, URI 객체로 지정할 수도 있다
문자열의 경우 전체 경로를 한꺼번에 지정하거나 상위 디렉토리와 하위 디렉토리를 나열하여도 좋다
ex) "C:\Temp\dir\file.txt"의 경로를 이용해 Path를 얻는 방법
Path path = Paths.get("C:\Temp\dir\file.txt");
Path path = Paths.get("C:\Temp\dir", "file.txt");
Path path = Paths.get("C:","Temp","dir", "file.txt");
파일의 경로는 절대 경로와 상대 경로를 사용가능하다
Path 인터페이스에는 파일 경로를 얻을 수 있는 여러 정보를 제공해주는 메서드가 있다
| 리턴 타입 | 메서드(매개변수) | 설명 |
| int | compareTo(Path other) | 파일 경로가 동일하면 0을 리턴, 상위 경로면 음수, 하위 경로면 양수 리턴, 음스와 양수 값의 차이나는 문자열 수 |
| Path | getFileName() | 부모 경로를 제외한 파일 또는 디렉토리 이름만 가진 Path 리턴 |
| FileSystem | getFileSystem() | FileSystem 객체 리턴 |
| Path | getName(int index) | C:\Temp\dir\file.txt 일 경우 index가 0이면 "Temp"의 Path 객체 리턴 index가 1이면 "dir"의 Path 객체 리턴 index가 2이면 "file.txt"의 Path 객체 리턴 |
| int | getNameCount() | 중첩 경로 수, C:\Temp\dir\file.txt일 경우 3을 리턴 |
| Path | getParent() | 바로 위 부모 폴더의 Path 리턴 |
| Path | getRoot() | 루트 디렉토리의 Path 리턴 |
| Iterator<Path> | iterator() | 경로에 있는 모든 디렉토리와 파일을 Path 객체로 생성 후 반복자 리턴 |
| Path | normalize() | 상대 경로로 표기할 때 불필요한 요소 제거 |
| WatchKey | register(...) | WatchService를 등록 |
| File | toFile() | java.io.file 객체로 리턴 |
| String | toString() | 파일 경로를 문자열로 리턴 |
| URI | tiUri() | 파일 경로를 URI 객체로 리턴 |
아래 예제를 통해 확인해 보자
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
public class PathExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("src/sec02/exam01_path/PathExample.java");
System.out.println("[파일명] " + path.getFileName());
System.out.println("[부모 디렉토리명]: " + path.getParent().getFileName());
System.out.println("중첩 경로수: " + path.getNameCount());
System.out.println();
for(int i=0; i<path.getNameCount(); i++) {
System.out.println(path.getName(i));
}
System.out.println();
Iterator<Path> iterator = path.iterator();
while(iterator.hasNext()) {
Path temp = iterator.next();
System.out.println(temp.getFileName());
}
}
}
결과는 아래와 같다

3. WatchService 인터페이스
자바에서 기본적으로 제공하는 API이며, Java 1.7 이후로 사용이 가능하다
WatchService 인터페이스는 특정 디렉토리를 감시하고 변경 이벤트 발생시,
지정한 액션을 수행하도록 도와준다
1) WatchService 생성
Path path 객체에 getParent()를 이용해 루트 경로를 담아준다
그리고 WatchService watchService 에 path.getFileSystem().newWatchService() 로
watchService 객체를 생성한다
WatchService watchService = path.getFileSystem().newWatchService();
2) WatchService 등록
WatchService 를 생성하고, 감시가 필요한 디렉토리인 위에서 설정한
path를 Path 객체에서 regiser() 메서드를 이용하여 WatchService를 등록한다
어떠한 변화(생성, 삭제, 수정)를 감시할지 StandardWatchEventKinds 상수로 지정할 수 있다
path.register(this.watchService
, StandardWatchEventKinds.ENTRY_CREATE
, StandardWatchEventKinds.ENTRY_DELETE
, StandardWatchEventKinds.ENTRY_MODIFY);
3) WatchKey 얻기
path에 WatchService를 등록한 순간부터 path 내부에서 변경이 발생하면
WatchEvent가 발생하고, WatchService는 해당 이벤트 정보를 가진 WatchKey를 생성해
Queue에 넣어준다
WatchKey에 이벤트가 들어올 떄 까지 대기를 한다
take() 를 이용해서 watchKey를 기다리기도 하지만
pollingInterval 을 이용해서 하는 방법도 있다
while(true) {
WatchKey watchKey = watchService.take(); // 큐에 watchKey가 들어올 때까지 대기
}
WatchKey watchKey = this.watchService.poll(pollingInterval, TimeUnit.MILLISECONDS);
WatchKey를 얻어내면 pollEvents() 메서드를 사용해서
WatchEvent 리스트를 얻어낸다
WatchEvent watchEvent = watchKey.pollEvents();
이렇게 얻어낸 WatchEvent에 종류와 path 객체를 얻어 처리한다
WatchEvent.Kind<?> kind = event.kind(); // 이벤트 종류
Path eventFile = (Path)event.context(); // 이벤트가 발생한 파일
// 이벤트 종류별로 처리
if(kind == StandardWatchEventKinds.ENTRY_CREATE) {
// 생성되었을 경우, 실행할 코드
} else if(kind == StandardWatchEventKinds.ENTRY_DELETE) {
// 삭제되었을 경우, 실행할 코드
} else if(kind == StandardWatchEventKinds.ENTRY_MODIFY) {
// 수정되었을 경우, 실행할 코드
}
4) WatchService 종료
한번 사용된 WatchKey는 reset() 메서드로 초기화를 해야한다
새로운 WatchEvent가 발생하면 큐에 다시 들어가기 때문이다
초기화에 성공하면 reset() 메서드는 true를 리턴하지만
감시하는 경로가 삭제되거나 키가 더 이상 유효하지 않을경우 false를 리턴한다
WatchKey가 더 이상 유효하지 않게 되면 무한루프를 빠져나와
WatchService의 close() 메서드를 호출하고 종료하면 된다
while(true) {
WatchKey watchKey = watchService.take();
WatchEvent watchEvent = watchKey.pollEvents();
// 생략
boolean resetFlag = watchKey.reset();
if(!resetFlag) { break; }
}
watchService.close();
4. FileChannel 클래스
위에서 변경 이벤트를 감지해서 파일의 변화를 알아냈다면
해당 파일의 내용을 읽어야 한다
1) open() 메서드를 사용하여 path의 채널을 생성해준다,
또한 생성한 채널의 파일의 읽기 위치도 설정한다
FileChannel.open(path, StandardOpenOption.READ)
// 파일을 읽기 시작하는 위치를 파일의 마지막으로 설정함
long offset = this.target.get(i).getFileName().toFile().length();
this.readChannel.get(i).get(this.target.get(i).getFileName()).position(offset);
2) ByteBuffer를 이용해 읽은 데이터를 저장할 데이터 버퍼를 생성한다
ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024 * 1024);
3) 더이상 읽을 바이트가 없을때 까지 반복해서 채널을 읽는다
path.read(readBuffer)) != -1
4) readBuffer.flip() 을 이용해서 읽기모드로 바꾼다
L을 P위치로 바꾸고, P를 0으로 바꿀뿐이다
flip이라는 뜻 자체가 "뒤집다"라서 쓰기모드를 읽기모드로 바꾸는 것을 의미한다
하지만 한번 더 호출한다고 해도 읽기모드에서 쓰기모드로 바꾸는 것은 아니다
readBuffer.flip();
5) ByteBuffer -> ByteArray 로 바꾼다
remaining() 은 읽을 수 있는 크기를 얻는 메서드이다
byte[] buffer = new byte[readBuffer.remaining()];
readBuffer.get(buffer);
6) \n 을 기준으로 읽을 데이터를 messages 변수에 담는다
ArrayList<byte[]> messages = CommonUtil.splitBytes(buffer, this.lineSeparator.getBytes());
7) messages 에서 하나씩 뽑아 message에 담고 반환한다
for (int j = 0; j < messages.size(); j++) {
byte[] message = messages.get(j);
logMessages.add(transformData(new String(message, this.charset)));
}
8) 반환이 완료된 값을 담았던 readBuffer 를 비워준다
readBuffer.clear();
'Java' 카테고리의 다른 글
| [Java] java.nio 활용 FileTracker 구현 3 (리팩토링) (0) | 2024.02.21 |
|---|---|
| [Java] java.nio 활용 FileTracker 구현 2 (복수 파일 처리) (1) | 2024.02.19 |
| [Java] Java volatile 이란? (1) | 2024.02.05 |
| [Java] Solace PubSub+ JCSMP, JMS, JAVA API (0) | 2024.01.16 |
| [Java] Solace PubSub+ 구축 및 웹 테스트 (0) | 2024.01.16 |