RUNTIME.EXEC() 에서 PROCESSBUILDER까지

Java SE 2005/09/13 10:13 Posted by Sun
JDK 5.0 이전의 경우에는, 특정 프로세스를 골라내서(fork off) 이를 사용자 runtime에 로컬로 실행할 수 있는 유일한 방법은java.lang.Runtime클래스의 exec() 메소드를 이용하는 것이었다. JDK 5.0은 ProcessBuilder라 불리는 클래스를 통해 별도의 프로세스로 명령어를 실행하는 새로운 방법을 추가로 제공하며, 이 ProcessBuilderjava.lang패키지에 포함되어 있다 (Runtime이나 Process처럼). 이제 이 팁에서는 두 가지 접근법을 설명하고 비교해 보도록 한다.

여러분이 Runtime클래스에 익숙하다면 메모리 사용량을 확인하고 종료 후크를 추가할 수 있다는 것을 알고 있을 것이다. 그러나 5.0 이전까지 가장 인기 있는 클래스 이용 방식은 아마도 별도의 프로세스로 명령어를 실행하는 것이었을 것이다. 이 작업은 아래와 같이 Runtimeexec() 메소드의 여섯 개 버전 중 하나를 통해 수행되었다.
public Process exec(String command)
             throws IOException

public Process exec(String command,
                    String[] envp)
             throws IOException

public Process exec(String command,
                    String[] envp,
                    File dir)
             throws IOException

public Process exec(String[] cmdarray)
             throws IOExceptionjava

public Process exec(String[] cmdarray,
                    String[] envp)
             throws IOException

public Process exec(String[] cmdarray,
                    String[] envp,
                    File dir)
             throws IOException
exec() 메소드를 호출하기 전에 명령어와 해당 독립변수, 환경 변수 설정, 작업 디렉터리 등을 지정한다. 모든 버전의 메소드는 생성된 프로세스를 관리하기 위한 java.lang.Process 오브젝트를 반환하는데, 이는 서브프로세스 및 종료 상태의 인풋 또는 아웃풋 스트림을 얻어낼 수 있도록 해준다 (가용한 다른 정보들 중에서).

다음은 원래의Runtime클래스로 명령어를 실행하는 방법을 보여주는 DoRuntime 예제이다. 실행할 명령어는 명령어 라인에서 전달된다.
   import java.io.*;
   import java.util.*;
   
   public class DoRuntime {
     public static void main(String args[]) throws IOException {
       if (args.length <= 0) {
         System.err.println("Need command to run");
         System.exit(-1);
       }
       Runtime runtime = Runtime.getRuntime();
       Process process = runtime.exec(args);
       InputStream is = process.getInputStream();
       InputStreamReader isr = new InputStreamReader(is);
       BufferedReader br = new BufferedReader(isr);
       String line;
       System.out.printf("Output of running %s is:", 
           Arrays.toString(args));
       while ((line = br.readLine()) != null) {
         System.out.println(line);
       }
     }
    } 
Solaris에서 아래처럼 DoRuntime을 실행할 경우 :
  java DoRuntime ls
다음과 유사한 아웃풋을 얻게 된다(디렉터리의 내용에 의해 좌우됨).:
  Output of running ls is:DoRuntime.class
  DoRuntime.java   
Linux 사용자는 또한 'Is'를 명령어로 사용하여 디렉터리 리스팅을 얻어낼 수도 있다.

Microsoft Windows 플랫폼에서 "dir" 같은 명령어는 명령어 프로세서에 내재하므로 단일 명령어 라인 독립변수는 인용된 문자열: "cmd /c dir" 이 될 것이다(이 경우에도 아웃풋은 디렉터리의 내용에 의해 좌우됨).
  >  java DoRuntime "cmd /c dir"
  Output of running cmd /c dir is: ...

  Directory of C:\...

  07/15/2005  09:30 AM    <DIR>          .
  07/15/2005  09:30 AM    <DIR>          ..
  07/15/2005  09:30 AM             1,146 DoRuntime.class
  07/15/2005  09:23 AM               724 DoRuntime.java
  ...
코딩된 명령어는 환경 변수가 바뀌지 않은 상태로 현재의 작업 디렉터리에서 실행된다

다른 디렉터리에서 명령어를 실행하기를 원하고 exec() 명령어에 더 많은 독립변수가 필요하다면 아래의 내용을
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command);
다음과 같이 바꾼다: :
    File file = new File(other directory);
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command, null, file);
exec() 메소드에 대한 호출에서 두 번째 파라미터는 환경 변수 설정을 가리키는데, 파라미터가 'null'이기 때문에 서브프로세스는 현재 프로세스의 환경 설정을 계승하게 된다.

그렇다면 이 접근법의 문제점은 무엇이며, 왜 새로운 접근법이 필요한 것일까? 문제는 Runtime.exec 접근법이 반드시 서브프로세스를 사용자정의하고 활 성화하는 일을 용이하게 해주지 않는다는 사실이다. 새로운ㅍ 클래스는 작업을 간소화시켜 주는데, 즉 클래스 내의 다양한 메소드를 통해 특정 프로세스에 대한 환경 변수를 쉽게 수정하고 프로세스를 시작할 수 있다.

다음은 DoRuntime 예제의 함수를 복제하는 간편한 ProcessBuilder 사용법이다
   import java.io.*;
   import java.util.*;
   
   public class DoProcessBuilder {
     public static void main(String args[]) throws IOException {
       if (args.length <= 0) {
         System.err.println("Need command to run");
         System.exit(-1);
       }
       Process process = new ProcessBuilder(args).start();
       InputStream is = process.getInputStream();
       InputStreamReader isr = new InputStreamReader(is);
       BufferedReader br = new BufferedReader(isr);
       String line;
       System.out.printf("Output of running %s is:", 
          Arrays.toString(args));
       while ((line = br.readLine()) != null) {
         System.out.println(line);
       }
     }
    } 
   
  > java DoProcessBuilder ls
  Output of running ls is:DoProcessBuilder.class 
  DoProcessBuilder.java
  DoRuntime.class
  DoRuntime.java   
DoProcessBuilder의 다음 두 행이:
    Runtime runtime = Runtime.getRuntime();
    Process process = runtime.exec(command);
DoProcessBuilder 내의 다음 행으로 바뀐 점에 유의하도록 한다.
    Process process = new ProcessBuilder(command).start();
ProcessBuilder클래스는 두 개의 생성자(constructor)를 가지는데, 한 생성자는 명령어에 대한 List와 그 독립변수를 적용하고, 다른 생성자는 가변적인 수의 String독립변수를 적용한다.
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
ProcessBuilder에서 start()를 호출하여 명령어를 실행하는데, start()를 호출하기 전에 Process가 생성되는 방식을 조작할 수 있다. 프로세스가 다른 디렉터리에서 시작되기를 원할 경우에는 File을 명령어 라인 독립변수로 전달하는 대신 Filedirectory() 메소드에 전달하여 프로세서 빌더의 작업 디렉터리를 설정한다.
public ProcessBuilder directory(File directory)
ProcessBuilder 에는 환경 변수 설정을 위해 명확하게 정해진 세터 타입의 메소드가 없다. 대신,environment()메소드를 통해 변수들의 Map을 얻어낸 다음 Map을 처리(조작)한다.
   ProcessBuilder processBuilder = new ProcessBuilder(command);
   Map<String, String> env = processBuilder.environment();
   // manipulate env
환경을 조작하기 위한 옵션에는 put() 메소드로 환경 변수를 추가하고 remove() 메소드로 제거하는 방법이 포함된다. 예:
   ProcessBuilder processBuilder = new ProcessBuilder(
                                       command, arg1, arg2);
   Map<String, String> env = processBuilder.environment();
   env.put("var1", "value");
   env.remove("var3");

환경 변수와 디렉터리가 설정된 후에start()를 호출한다:
   processBuilder.directory("Dir");
   Process p = processBuilder.start();
또한, 환경으로부터 모든 변수를 clear()하고 원하는 변수를 명확하게 설정할 수도 있다.

프로세스 공간으로부터 환경 변수들을 추가하고 제거하는 environment()나 새 프로세스를 시작하는 start() 같은 메소드를 보유한 ProcessBuilder는 수정된 프로세스 환경으로 서브프로세스를 활성화하는 것을 용이하게 해주어야 한다.

Systemgetenv()메소드를 호출하여 초기의 환경 변수 세트를 얻어낼 수 있다. 환경 변수를 변경하는 것이 금지된 플랫폼에서 환경 변수를 변경하려고 하면 UnsupportedOperationException 또는 IllegalArgumentException중 하나가 전달된다. 또한, 보안 매니저와 함께 실행할 경우에는 "getenv.*"에 RuntimePermission이 필요한데 , 이를 사용하지 않으면 SecurityException이 전달된다.

인스턴스를 구성한 후에는 start() 호출을 잊지 않도록 유의해야 하고, 아울러 프로세스 스트림을 관리하고 종료 상태를 얻어내기 위해서는 지속적으로 Process 클래스를 사용하도록 한다.

본 테크팁 예제에 관한 주의사항은 다음과 같다. 서브프로세스가 시스템을 오버플로(overflow)하기에 충분한 아웃풋을 생성하게 될 경우 예제들이 교착상태에 빠질 수 있다. 따라서 보다 안전한 방법은 별도의 쓰레드에서 프로세스 stdout 및 stderr를 사용하는 것이다.

ProcessBuilder에 관한 자세한 내용은class definition을 참조하도록 한다.

"Java SE" 카테고리의 다른 글

2005/09/13 10:13 2005/09/13 10:13

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/181

댓글을 달아 주세요

  1. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 05:05
[로그인][오픈아이디란?]

◀ Prev 1  ... 489 490 491 492 493 494 495 496 497  ... 626  Next ▶