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:
- 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
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.
- 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);
- }
- }
- }
You get output that looks something like this (which depends on the contents of the directory):
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:
- 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);
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:
- 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);
- }
- }
- }
- Runtime runtime = Runtime.getRuntime();
- Process process = runtime.exec(command);
- Process process = new ProcessBuilder(command).start();
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:
- public ProcessBuilder directory(File directory)
- ProcessBuilder processBuilder = new ProcessBuilder(command);
- Map env = processBuilder.environment();
- // manipulate env
- ProcessBuilder processBuilder = new ProcessBuilder(
- command, arg1, arg2);
- Map env = processBuilder.environment();
- env.put("var1", "value");
- env.remove("var3");
- processBuilder.directory("Dir");
- Process p = processBuilder.start();
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.
沒有留言:
張貼留言