Java ProcessBuilder와 Process API
ProcessBuilder
와 Process
클래스는 자바에서 외부 프로세스를 실행하고 관리하기 위해 사용됩니다. 이 클래스들을 사용하면 자바 코드 내에서 다른 애플리케이션을 실행하고, 그 입력과 출력을 관리할 수 있습니다. 아래에서는 ProcessBuilder
와 Process
클래스의 주요 기능과 메소드들을 상세히 설명하겠습니다.
ProcessBuilder 클래스
ProcessBuilder
클래스는 프로세스의 실행 환경을 설정하는 데 사용됩니다. 이 클래스의 인스턴스는 실행할 프로그램과 그 인자들을 설정하고, 프로세스의 환경 변수와 작업 디렉토리를 구성할 수 있습니다.
주요 생성자와 메소드
- 생성자
ProcessBuilder(String... command)
: 실행할 명령과 그 인자들을 받습니다.ProcessBuilder(List<String> command)
: 실행할 명령과 그 인자들을 리스트 형태로 받습니다.
- 메소드
start()
: 설정된 명령어로 새 프로세스를 시작합니다.command(String... command)
: 실행할 명령을 설정합니다.command(List<String> command)
: 실행할 명령을 리스트로 설정합니다.environment()
: 프로세스의 환경 변수를 반환합니다. 이를 수정하면 프로세스의 환경 변수를 변경할 수 있습니다.directory(File dir)
: 프로세스가 실행될 디렉토리를 설정합니다.redirectInput(ProcessBuilder.Redirect source)
: 프로세스의 표준 입력을 리다이렉션합니다.redirectOutput(ProcessBuilder.Redirect destination)
: 프로세스의 표준 출력을 리다이렉션합니다.redirectError(ProcessBuilder.Redirect destination)
: 프로세스의 표준 에러를 리다이렉션합니다.redirectErrorStream(boolean redirectErrorStream)
: 표준 에러 출력을 표준 출력과 병합할지 여부를 설정합니다.
Process 클래스
Process
클래스는 실행 중인 프로세스를 대표하며, 프로세스의 입력, 출력 및 종료를 관리합니다.
주요 메소드
getOutputStream()
: 프로세스의 표준 입력 스트림에 데이터를 쓰기 위한OutputStream
을 반환합니다.getInputStream()
: 프로세스의 표준 출력 스트림에서 데이터를 읽기 위한InputStream
을 반환합니다.getErrorStream()
: 프로세스의 표준 에러 스트림에서 데이터를 읽기 위한InputStream
을 반환합니다.waitFor()
: 프로세스가 종료될 때까지 현재 스레드를 대기시킵니다. 종료 시 프로세스의 종료 코드를 반환합니다.exitValue()
: 프로세스의 종료 코드를 반환합니다. 프로세스가 아직 종료되지 않았을 경우 예외를 발생시킵니다.destroy()
: 프로세스를 강제로 종료합니다.
사용 예제
아래는 ProcessBuilder
를 사용하여 외부 프로그램을 실행하고 그 결과를 출력하는 간단한 예제입니다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ProcessExample {
public static void main(String[] args) {
ProcessBuilder builder = new ProcessBuilder("yourCommand", "arg1", "arg2");
builder.redirectErrorStream(true); // 에러 출력을 표준 출력에 병합
try {
Process process = builder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("Process exited with code: " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
이 예제는 외부 명령을 실행하고, 그 출력을 읽으며, 프로세스가 종료될 때까지 기다립니다. 표준 에러 스트림을 표준 출력에 병합하여 모든 출력을 한 곳에서 처리할 수 있게 합니다. 이러한 기능을 활용하여 다양한 외부 프로세스를 효과적으로 관리할 수 있습니다.
ProcessBuilder
를 사용하여 프로세스의 표준 출력(getInputStream()
)과 표준 에러 스트림(getErrorStream()
)을 동시에 읽으려면 몇 가지 접근 방법이 있습니다. 이러한 방법 중 하나는 프로세스를 시작하기 전에 표준 에러 스트림을 표준 출력 스트림으로 리다이렉션하는 것입니다. 이를 통해 두 스트림을 하나로 통합하여 한 번에 읽을 수 있습니다.
표준 에러를 표준 출력으로 리다이렉션하기
ProcessBuilder.redirectErrorStream(true)
메소드를 사용하면 표준 에러 스트림을 표준 출력 스트림으로 리다이렉션할 수 있습니다. 이 설정을 활성화하면, Process.getInputStream()
을 통해 두 스트림의 데이터를 모두 읽을 수 있습니다.
예제 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class CombinedStreamExample {
public static void main(String[] args) {
ProcessBuilder builder = new ProcessBuilder("yourCommand", "arg1", "arg2");
builder.redirectErrorStream(true); // 에러 스트림을 출력 스트림으로 리다이렉션
try {
Process process = builder.start();
// 통합된 출력 및 에러 스트림 읽기
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 프로세스 종료 대기
int exitCode = process.waitFor();
System.out.println("Exited with code " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
별도의 스레드에서 스트림 읽기
만약 표준 출력과 표준 에러를 분리해서 로깅하거나 다르게 처리해야 하는 경우, 각 스트림을 별도의 스레드에서 읽는 방법이 있습니다. 이 방식은 스트림을 분리하여 동시에 읽을 수 있게 하며, 한 스트림의 버퍼가 가득 차서 프로세스가 정지되는 것을 방지합니다.
예제 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class StreamGobbler implements Runnable {
private InputStream inputStream;
private String type;
public StreamGobbler(InputStream inputStream, String type) {
this.inputStream = inputStream;
this.type = type;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(type + "> " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class SeparateStreamExample {
public static void main(String[] args) {
ProcessBuilder builder = new ProcessBuilder("yourCommand", "arg1", "arg2");
try {
Process process = builder.start();
// 표준 출력과 에러를 별도의 스레드에서 처리
Thread outputGobbler = new Thread(new StreamGobbler(process.getInputStream(), "OUTPUT"));
Thread errorGobbler = new Thread(new StreamGobbler(process.getErrorStream(), "ERROR"));
outputGobbler.start();
errorGobbler.start();
// 스레드가 종료되기를 기다림
outputGobbler.join();
errorGobbler.join();
// 프로세스 종료 대기
int exitCode = process.waitFor();
System.out.println("Exited with code " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
표준 에러처리
ProcessBuilder
를 사용하여 외부 프로세스를 실행할 때, 오류 처리는 매우 중요합니다. 프로세스가 실패하거나 예상치 못한 출력을 생성할 경우, 이를 적절히 처리하여 애플리케이션의 안정성을 유지해야 합니다. 다음은 프로세스 에러 처리를 위한 몇 가지 주요 방법입니다:
1. 표준 에러 스트림 읽기
프로세스가 생성하는 에러 메시지를 읽기 위해 표준 에러 스트림(getErrorStream()
)을 활용합니다. 이를 통해 발생한 문제를 파악하고 로그에 기록할 수 있습니다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ProcessErrorExample {
public static void main(String[] args) {
ProcessBuilder builder = new ProcessBuilder("yourCommand");
try {
Process process = builder.start();
// 표준 에러 스트림에서 에러 메시지 읽기
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line;
while ((line = errorReader.readLine()) != null) {
System.err.println("Error: " + line);
}
// 프로세스 종료 코드 확인
int exitCode = process.waitFor();
if (exitCode != 0) {
System.err.println("Process failed with exit code " + exitCode);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 종료 코드 검사
프로세스의 종료 코드를 검사하여 프로세스가 성공적으로 완료되었는지 확인합니다. 0
이 아닌 종료 코드는 일반적으로 오류를 나타냅니다.
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Process failed with exit code " + exitCode);
}
3. 예외 처리
ProcessBuilder.start()
메소드는 IOException
을 발생시킬 수 있으며, waitFor()
메소드는 InterruptedException
을 발생시킬 수 있습니다. 이러한 예외들을 적절히 처리해야 합니다.
try {
Process process = builder.start();
int exitCode = process.waitFor();
// Handle exit code or other conditions here
} catch (IOException e) {
e.printStackTrace(); // Handle IO problems, such as command not found
} catch (InterruptedException e) {
e.printStackTrace(); // Handle the interruption during the waitFor
}
4. 리소스 관리
프로세스의 입출력 스트림은 적절히 닫아 주어야 합니다. Java 7 이상에서는 try-with-resources 문을 사용하여 자동으로 리소스를 관리할 수 있습니다.
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
이러한 방법을 통해 외부 프로세스 실행 시 발생할 수 있는 다양한 오류 상황을 효과적으로 관리할 수 있습니다. 이를 통해 애플리케이션의 안정성과 오류 대응 능력을 높일 수 있습니다.