2014年9月17日 星期三

[ Java 文章收集 ] From Runtime.exec() to ProcessBuilder

Source From Here 
Preface 
Before JDK 5.0, the only way to fork off a process and execute it local to the user runtime was to use the exec() method of the java.lang.Runtime class. JDK 5.0 adds a new way of executing a command in a separate process, through a class called ProcessBuilder. You can find ProcessBuilder in the java.lang package (like Runtime andProcess). This tip discusses and compares both approaches. 

Runtime's exec() 
If you're familiar with the Runtime class, you know that it also allows you to discover memory usage and add a shutdown hook. But probably the most popular use of the class prior to 5.0 was to execute a command in a separate process. This was done through one of the six versions of the exec() method of Runtime
  1. public Process exec(String command)  
  2.              throws IOException  
  3.   
  4. public Process exec(String command,  
  5.                     String[] envp)  
  6.              throws IOException  
  7.   
  8. public Process exec(String command,  
  9.                     String[] envp,  
  10.                     File dir)  
  11.              throws IOException  
  12.   
  13. public Process exec(String[] cmdarray)  
  14.              throws IOExceptionjava  
  15.   
  16. public Process exec(String[] cmdarray,  
  17.                     String[] envp)  
  18.              throws IOException  
  19.   
  20. public Process exec(String[] cmdarray,  
  21.                     String[] envp,  
  22.                     File dir)  
  23.              throws IOException  
Before you call the exec() method, you specify the command and its arguments, environment variable settings, and working directory. All versions of the method return ajava.lang.Process object for managing the created process. This allows you to get the input or output stream of the subprocess and exit status (among other available information). 

Here's an example, DoRuntime, that shows how to execute a command with the original Runtime class. The command to run is passed in from the command line. 
  1. import java.io.*;  
  2.    import java.util.*;  
  3.      
  4.    public class DoRuntime {  
  5.      public static void main(String args[]) throws IOException {  
  6.   
  7.        if (args.length <= 0) {  
  8.          System.err.println("Need command to run");  
  9.          System.exit(-1);  
  10.        }  
  11.   
  12.        Runtime runtime = Runtime.getRuntime();  
  13.        Process process = runtime.exec(args);  
  14.        InputStream is = process.getInputStream();  
  15.        InputStreamReader isr = new InputStreamReader(is);  
  16.        BufferedReader br = new BufferedReader(isr);  
  17.        String line;  
  18.   
  19.        System.out.printf("Output of running %s is:",   
  20.            Arrays.toString(args));  
  21.   
  22.        while ((line = br.readLine()) != null) {  
  23.          System.out.println(line);  
  24.        }  
  25.   
  26.      }  
  27.     }  
If you run DoRuntime in Solaris like this: 
$ java DoRuntime ls

You get output that looks something like this (which depends on the contents of the directory): 
Output of running ls is:DoRuntime.class
DoRuntime.java

As coded, the command executes in the current working directory with its environment variables intact. 

If you want to run the command in a different directory, and you need to add more arguments to the exec() command, you change: 
  1. Runtime runtime = Runtime.getRuntime();  
  2. Process process = runtime.exec(command);  
To: 
  1. File file = new File(other directory);  
  2. Runtime runtime = Runtime.getRuntime();  
  3. Process process = runtime.exec(command, null, file);  
The second parameter in the call to the exec() method identifies the environment variable settings. Because the parameter is "null", the subprocess inherits the environment settings of the current process

So what's wrong with this approach? Why create a new approach? The problem is that the Runtime.exec approach doesn't necessarily make it easy to customize and invoke subprocesses. The new ProcessBuilder class simplifies things. Through various methods in the class, you can easily modify the environment variables for a process and start the process. 

ProcessBuilder's Introduction 
Here's a simple use of ProcessBuilder that duplicates the functions of the DoRuntime example: 
  1. import java.io.*;  
  2.    import java.util.*;  
  3.      
  4.    public class DoProcessBuilder {  
  5.      public static void main(String args[]) throws IOException {  
  6.   
  7.        if (args.length <= 0) {  
  8.          System.err.println("Need command to run");  
  9.          System.exit(-1);  
  10.        }  
  11.   
  12.        Process process = new ProcessBuilder(args).start();  
  13.        InputStream is = process.getInputStream();  
  14.        InputStreamReader isr = new InputStreamReader(is);  
  15.        BufferedReader br = new BufferedReader(isr);  
  16.        String line;  
  17.   
  18.        System.out.printf("Output of running %s is:",   
  19.           Arrays.toString(args));  
  20.   
  21.        while ((line = br.readLine()) != null) {  
  22.          System.out.println(line);  
  23.        }  
  24.   
  25.      }  
  26.     }   
Notice that the following two lines in DoRuntime: 
  1. Runtime runtime = Runtime.getRuntime();  
  2. Process process = runtime.exec(command);  
were changed to the following line in DoProcessBuilder
  1. Process process = new ProcessBuilder(command).start();  
The ProcessBuilder class has two constructors. One constructor accepts a List for the command and its arguments. The other constructor accepts a variable number of String arguments. 
* ProcessBuilder(List command): Constructs a process builder with the specified operating system program and arguments.
* ProcessBuilder(String... command): Constructs a process builder with the specified operating system program and arguments.

With ProcessBuilder, you call start() to execute the command. Prior to calling start(), you can manipulate how the Process will be created. If you want the process to start in a different directory, you don't pass a File in as a command line argument. Instead, you set the process builder's working directory by passing the File to the directory()method: 
  1. public ProcessBuilder directory(File directory)  
There isn't an obvious setter type method in ProcessBuilder for setting environment variables. Instead, you get a Map of the variables through the environment() method, then you manipulate the Map: 
  1. ProcessBuilder processBuilder = new ProcessBuilder(command);  
  2. Map env = processBuilder.environment();  
  3. // manipulate env  
The options for manipulating the environment include adding environment variables with the put() method, and removing them with the remove() method. For example: 
  1. ProcessBuilder processBuilder = new ProcessBuilder(  
  2.                                        command, arg1, arg2);  
  3. Map env = processBuilder.environment();  
  4. env.put("var1""value");  
  5. env.remove("var3");  
After the environment variables and directory are set, call start(): 
  1. processBuilder.directory("Dir");  
  2. Process p = processBuilder.start();  
You can also clear() all the variables from the environment and explicitly set the ones you want. 

With methods such as environment() for adding and removing environment variables from the process space, and start() for starting a new process,ProcessBuilder should make it easier to invoke a subprocess with a modified process environment. 

You can get the initial set of environment variables by calling the getenv() method of System. Understand that not all platforms support changing environment variables. If you try to change an environment variable on a platform that forbids it, the operation will throw either an UnsupportedOperationException or anIllegalArgumentException. Also, when running with a security manager, you'll need the RuntimePermission for "getenv.*", otherwise a SecurityException will be thrown.

Remember not to forget the start() call after configuring your instance. And, keep using the Process class to manipulate the streams for the process and to get its exit status.

A word of caution about the examples in this tip. It is possible that the examples will deadlock if the subprocess generates enough output to overflow the system. A more robust solution requires draining the process stdout and stderr in separate threads.

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...