회사에서 데이터를 자동으로 수집하고 처리하여 모니터링할 수 있는 페이지를 구현해야 하는 작업이 있었다. 이를 위해 Spring 프로젝트 내에서 Jython을 활용하려고 했으나, Python의 라이브러리가 더 다양하고 익숙하다고 판단되어 프로젝트 내에서 Python을 실행하는 방향으로 결정되었다.
프로젝트를 진행하면서 발생된 이슈와 과정을 포스팅하고 정리해본다.
ProcessBuilder는 자바에서 외부 프로그램을 실행하고 관리하기 위한 도구로, 해당 클래스를 사용하여 외부 프로그램의 실행, 인자 전달, 실행 결과 가져오기, 입출력 스트림 관리, 오류 처리 등을 편리하게 수행할 수 있다.
ProcessBuilder processBuilder = new ProcessBuilder("python", "test.py", "arg1","agr2");
Process process = processBuilder.start();
ProcessBuilder("C:/python/python311/bin..", (생략))
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
// 실행 결과 처리
System.out.println(line);
}
// 1. 오류 메시지 출력
InputStream errorStream = process.getErrorStream();
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream));
String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
// 오류 처리
}
// 2. 프로세스 종료 대기
int exitCode = process.waitFor();
System.out.println("종료 코드: " + exitCode);
// 3. 프로세스 강제 종료
process.destroy();
// 4. 종료 코드 확인
int exitCode = process.exitValue();
System.out.println("외부 프로그램 종료 코드: " + exitCode);
일반적으로 발생하는 종료코드는 다음과 같다.
arg1
과 agr2
를 전달하였다. 실제 파이썬 파일에서는 전달 받은 데이터를 어떻게 읽어오는지 보자.import sys
# 데이터 수집 및 처리 함수 예시
def toUpper(data):
# 데이터 처리 작업 수행
upstr = data.upper()
return upstr
# 데이터 입력 받기
data = sys.argv[1:]
print(data)
# 데이터 처리 함수 호출
result = [toUpper(upstr) for upstr in data]
# 결과 출력
print(result)
# result
#['arg1', 'arg2'] 입력
#['ARG1', 'ARG2'] 출력
from tqdm import tqdm
import time
for i in tqdm(range(10)):
print(i)
time.sleep(1)
실행한 파일을 한 줄 씩 읽어 실시간으로 출력되도록 하기위해
간단한 반복문 예제를 테스트해보도록 한다. tqdm 라이브러리까지 준비했다.
하지만 해당 ProcessBuilder는 실행되는 동안의 출력 값이 실시간으로 찍히는 것이 아니고, 실행이 끝나고 나서야 한꺼번에 로그에 찍혔다.(ㅜㅜ)
결론적으로는 stack overflow 에서 해답을 찾았다.
python 파일의 print 함수에 flush를 True로 설정하면 된다.
flush : 버퍼에 저장된 데이터를 강제로 출력하는 동작
print(i, flush=True)
이후 ProcessBuilder를 대체할 수 있는 다른 방법은 없는지 더 찾아보기로 했다.
ProcessBuilder와 유사하나, 비동기 실행, 스트림 리다이렉션, 타임아웃 설정, 중단 등의 기능을 추가로 제공하여 외부 프로그램과의 상호작용을 더 효율적으로 관리할 수 있는 라이브러리
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>1.12</version>
</dependency>
new ProcessExecutor().command("python", "test.py")
.redirectOutput(new LogOutputStream() {
@Override
protected void processLine(String line) {
System.out.println(line);
}
})
.execute();
ProcessExecutor 에서 다양한 함수가 제공되는 것 같아, 추후에 사용할 수 있을만한 재밌는 몇 가지 예제도 한번 테스트해보고 싶다.
자세한 내용과 예제는 아래 깃허브를 통해서 확인할 수 있다.
https://github.com/zeroturnaround/zt-exec
결과적으로는 zt-exec에서 제공하는 ProcessExecutor를 사용하기로 하였다. 또한 tqdm 출력 부분을 활용한다면 추가적인 코드 없이 뷰에서 ProgressBar 형태로 제공해도 될 것 같다.
다음 포스팅에서는 WebSocket을 사용하여 뷰에서 파일의 진행 상황을 실시간으로 확인할 수 있는 방법에 대해 다루어 보겠다.