此类用于创建操作系统进程。
每个 ProcessBuilder
实例管理一个进程属性集。start()
方法利用这些属性创建一个新的 Process
实例。start()
方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。
每个进程生成器管理这些进程属性:
- 命令 是一个字符串列表,它表示要调用的外部程序文件及其参数(如果有)。在此,表示有效的操作系统命令的字符串列表是依赖于系统的。例如,每一个总体变量,通 常都要成为此列表中的元素,但有一些操作系统,希望程序能自己标记命令行字符串——在这种系统中,Java 实现可能需要命令确切地包含这两个元素。
- 环境 是从变量 到值 的依赖于系统的映射。初始值是当前进程环境的一个副本(请参阅
System.getenv()
)。 - 工作目录。默认值是当前进程的当前工作目录,通常根据系统属性
user.dir
来命名。 - redirectErrorStream 属性。最初,此属性为
false
,意思是子进程的标准输出和错误输出被发送给两个独立的流,这些流可以通过Process.getInputStream()
和Process.getErrorStream()
方法来访问。如果将值设置为true
,标准错误将与标准输出合并。这使得关联错误消息和相应的输出变得更容易。在此情况下,合并的数据可从Process.getInputStream()
返回的流读取,而从Process.getErrorStream()
返回的流读取将直接到达文件尾。
修改进程构建器的属性将影响后续由该对象的 start()
方法启动的进程,但从不会影响以前启动的进程或 Java 自身的进程。
大多数错误检查由 start()
方法执行。可以修改对象的状态,但这样 start()
将会失败。例如,将命令属性设置为一个空列表将不会抛出异常,除非包含了 start()
。
注意,此类不是同步的。如果多个线程同时访问一个 ProcessBuilder
,而其中至少一个线程从结构上修改了其中一个属性,它必须 保持外部同步。
很容易启动一个使用默认工作目录和环境的新进程:
Process p = new ProcessBuilder("myCommand", "myArg").start();
下面是一个利用修改过的工作目录和环境启动进程的例子:
ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2"); Map<String, String> env = pb.environment(); env.put("VAR1", "myValue"); env.remove("OTHERVAR"); env.put("VAR2", env.get("VAR1") + "suffix"); pb.directory(new File("myDir")); Process p = pb.start();
要利用一组明确的环境变量启动进程,在添加环境变量之前,首先调用 Map.clear()
。
System.getenv()
)。由此对象的
start()
方法启动的后续子进程将使用这一映射作为它们的环境。
可以使用普通的 Map
操作来修改返回的对象。对于通过 start()
方法启动的子进程,这些修改是可见的。两个 ProcessBuilder
实例总是包含独立的进程环境,因此,针对返回的映射的更改从不会在任何其他 ProcessBuilder
实例或由 System.getenv
返回的值中反映出来。
如果系统不支持环境变量,将返回空映射。
返回的映射不允许空键或空值。试图插入空键或空值或者试图查询它们的存在,都将抛出 NullPointerException
。试图查询非 String
类型的键或值的存在,都将抛出 ClassCastException
。
返回的映射的行为取决于系统。系统可能不允许修改环境变量或禁止某些变量名或变量值。出于此原因,如果不允许操作系统修改的话,试图修改映射可能失败,并抛出 UnsupportedOperationException
或 IllegalArgumentException
。
由于环境变量名和值的外部格式取决于系统,在它们与 Java 的 Unicode 字符串之间不可能是一对一映射。此外,映射以这种方式实现:不能由 Java 代码修改的环境变量在子进程中将有一个不可修改的本机表示形式。
返回的映射及其集合视图不能遵守 Object.equals(java.lang.Object)
和 Object.hashCode()
方法的常规协定。
返回的映射通常在所有平台上都是区分大小写的。
如果安全管理器存在,则其 checkPermission
方法通过
权限进行调用。这可能导致抛出 RuntimePermission
("getenv.*")SecurityException
。
当将信息传递给 Java 子进程时,系统属性通常优先于环境变量。
public Map<String,String> environment() { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(new RuntimePermission("getenv.*")); if (environment == null) environment = ProcessEnvironment.environment(); assert environment != null; return environment; } // Only for use by Runtime.exec(...envp...) ProcessBuilder environment(String[] envp) { assert environment == null; if (envp != null) { environment = ProcessEnvironment.emptyEnvironment(envp.length); assert environment != null; for (String envstring : envp) { // Before 1.5, we blindly passed invalid envstrings // to the child process. // We would like to throw an exception, but do not, // for compatibility with old broken code. // Silently discard any trailing junk. if (envstring.indexOf((int) '/u0000') != -1) envstring = envstring.replaceFirst("/u0000.*", ""); int eqlsign = envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH); // Silently ignore envstrings lacking the required `='. if (eqlsign != -1) environment.put(envstring.substring(0,eqlsign), envstring.substring(eqlsign+1)); } } return this; } /** * Returns this process builder's working directory. * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. * The returned value may be <code>null</code> -- this means to use * the working directory of the current Java process, usually the * directory named by the system property <code>user.dir</code>, * as the working directory of the child process.</p> * * @return This process builder's working directory */ public File directory() { return directory; } /** * Sets this process builder's working directory. * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. * The argument may be <code>null</code> -- this means to use the * working directory of the current Java process, usually the * directory named by the system property <code>user.dir</code>, * as the working directory of the child process.</p> * * @param directory The new working directory * @return This process builder */ public ProcessBuilder directory(File directory) { this.directory = directory; return this; } /** * Tells whether this process builder merges standard error and * standard output. * * <p>If this property is <code>true</code>, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. * The initial value is <code>false</code>.</p> * * @return This process builder's <code>redirectErrorStream</code> property */ public boolean redirectErrorStream() { return redirectErrorStream; } /** * Sets this process builder's <code>redirectErrorStream</code> property. * * <p>If this property is <code>true</code>, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. * The initial value is <code>false</code>.</p> * * @param redirectErrorStream The new property value * @return This process builder */ public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) { this.redirectErrorStream = redirectErrorStream; return this; } 使用此进程生成器的属性启动一个新进程。在 directory()
指定的工作目录中,利用 environment()
指定的进程环境,新进程将调用由 command()
给出的命令和参数。
此方法检查命令是否为一条有效的操作系统命令。哪条命令是有效的呢?这取决于系统,但至少命令必须是非空字符串的非空列表。
如果有安全管理器,则用此对象的 command
数组的首个元素作为其参数来调用安全管理器的 checkExec
方法。这可能导致抛出 SecurityException
。
启动操作系统进程的方式完全取决于系统。其中有很多方面会导致错误:
- 未找到操作系统程序文件。
- 对程序文件的访问被拒绝。
- 工作目录不存在。
在这些情况中将会抛出一个异常。该异常的具体本质取决于系统,但它总是 IOException
的一个子类。
针对此进程生成器的后续修改将不会影响返回的 Process
。