首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Runtime.exec() 的圈套

2012-11-26 
Runtime.exec() 的陷阱原文地址:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page

Runtime.exec() 的陷阱

原文地址:http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4

作为Java语言的一部分。java.lang包被隐藏的导入到每一个Java程序。这个包的表面陷阱,经常影响到大多数程序员。这个月,我将讨论运行时exec()方法时的潜伏陷阱。

陷阱4:当运行exec()不会
java.lang.Runtime类,突出了静态方法calledgetRuntime(),,它会检索当前的Java运行时环境。这是唯一的方法来获取Runtime对象的引用。获取该引用,您通过可以调用Runtime类的exec()方法运行外部程序。开发人员经常调用这个方法来启动浏览器显示一个HTML帮助页面。

exec()有四个重载:

import java.util.*;import java.io.*;public class BadExecJavac2{    public static void main(String args[])    {        try        {                        Runtime rt = Runtime.getRuntime();            Process proc = rt.exec("javac");            int exitVal = proc.waitFor();            System.out.println("Process exitValue: " + exitVal);        } catch (Throwable t)          {            t.printStackTrace();          }    }}

不幸的是,一个运行的BadExecJavac2不产生任何输出。程序挂起、一直未完成。为什么javac进程一直没有完成?

为什么 Runtime.exec() 挂起
    JDK的Javadoc文档提供了这个问题的答案:

因为一些本机平台只提供有限的缓冲区大小为标准输入和输出流,未能及时写输入流或读取输出流的子流程可能会导致子流程阻止,甚至死锁。

    这只是程序员不阅读文档的一个案例。隐含常听到的建议:读好手册(RTFM)?答案是部分是的。在这种情况下,阅读Javadoc将让你停在半途;它解释说,你需要处理流到你的外部过程,但是它没有告诉你怎样做。
        这个问题,原因明显是大量程序员的问题和误解这个API有关信息:尽管运行exec()和流程API看起来非常简单,那简单是欺骗,因为简单,或者说是明显,使用的API是容易出错。这里给API设计师的建议是,为简单的操作保留简单的API。容易产生复杂性操作和具有特定平台依赖性应该准确反映问题域。有可能某个抽象进行的太深。这个JConfig库提供了一个示例的一个更完整的API来处理文件和流程操作(请参阅下面的Resources参考资料以获得更多信息)。


    现在,让我们遵循JDK文档和处理输出的javac过程。当您运行javac不带任何参数,它产生一组使用语句,描述了如何运行这个程序及其意义的所有可用的程序的选项。了解这些信息会到stderr(标准错误)流,您可以很容易地编写一个程序,在等待进程退出前检测这个输出流。

    清单4.3完成这个任务。虽然这种方法可以运行,但这不是一个好的通用解决方案。因此,清单4.3的程序被命名为MediocreExecJavac;它只提供了一个平庸的解决方案。一个更好的解决方案将不输出或者清空标准错误流和标准输出流。最好的解决方案将清空这些流(我之后将证明)。

清单 4.3 MediocreExecJavac.java
import java.util.*;import java.io.*;public class MediocreExecJavac{    public static void main(String args[])    {        try        {                        Runtime rt = Runtime.getRuntime();            Process proc = rt.exec("javac");            InputStream stderr = proc.getErrorStream();            InputStreamReader isr = new InputStreamReader(stderr);            BufferedReader br = new BufferedReader(isr);            String line = null;            System.out.println("<ERROR>");            while ( (line = br.readLine()) != null)                System.out.println(line);            System.out.println("</ERROR>");            int exitVal = proc.waitFor();            System.out.println("Process exitValue: " + exitVal);        } catch (Throwable t)          {            t.printStackTrace();          }    }}

运行MediocreExecJava产生:
E:\classes\com\javaworld\jpitfalls\article2>java MediocreExecJavac<ERROR>Usage: javac <options> <source files>where <options> includes:  -g                     Generate all debugging info  -g:none                Generate no debugging info  -g:{lines,vars,source} Generate only some debugging info  -O                     Optimize; may hinder debugging or enlarge class files  -nowarn                Generate no warnings  -verbose               Output messages about what the compiler is doing  -deprecation           Output source locations where deprecated APIs are used  -classpath <path>      Specify where to find user class files  -sourcepath <path>     Specify where to find input source files  -bootclasspath <path>  Override location of bootstrap class files  -extdirs <dirs>        Override location of installed extensions  -d <directory>         Specify where to place generated class files  -encoding <encoding>   Specify character encoding used by source files  -target <release>      Generate class files for specific VM version</ERROR>Process exitValue: 2

    所以,MediocreExecJavac运行产生一个退出值2。通常,一个退出值0表示成功,任何非零值表示一个错误。这些退出值的含义取决于特定的操作系统。一个Win32错误值为2是一个“未找到文件”错误。这是有道理的,因为javac期望我们遵循的程序源代码文件进行编译。

    因此,绕过第二个陷阱——永远挂在运行时exec()——如果你运行的程序产生输出或期望的输入,确保程序的输入和输出流。

假设一个命令是一个可执行程序
    在Windows操作系统,许多新程序员运行exec(),当试图使用它来完成为非执行命令,像dir和复制,他们就掉进了运行exec()的第三陷阱。清单4.4展示的正是这种情况:

清单 4.4 BadExecWinDir.java
import java.util.*;import java.io.*;public class BadExecWinDir{    public static void main(String args[])    {        try        {                        Runtime rt = Runtime.getRuntime();            Process proc = rt.exec("dir");            InputStream stdin = proc.getInputStream();            InputStreamReader isr = new InputStreamReader(stdin);            BufferedReader br = new BufferedReader(isr);            String line = null;            System.out.println("<OUTPUT>");            while ( (line = br.readLine()) != null)                System.out.println(line);            System.out.println("</OUTPUT>");            int exitVal = proc.waitFor();                        System.out.println("Process exitValue: " + exitVal);        } catch (Throwable t)          {            t.printStackTrace();          }    }}

运行BadExecWinDir输出:
import java.util.*;import java.io.*;class StreamGobbler extends Thread{    InputStream is;    String type;        StreamGobbler(InputStream is, String type)    {        this.is = is;        this.type = type;    }        public void run()    {        try        {            InputStreamReader isr = new InputStreamReader(is);            BufferedReader br = new BufferedReader(isr);            String line=null;            while ( (line = br.readLine()) != null)                System.out.println(type + ">" + line);                } catch (IOException ioe)              {                ioe.printStackTrace();                }    }}public class GoodWindowsExec{    public static void main(String args[])    {        if (args.length < 1)        {            System.out.println("USAGE: java GoodWindowsExec <cmd>");            System.exit(1);        }                try        {                        String osName = System.getProperty("os.name" );            String[] cmd = new String[3];            if( osName.equals( "Windows NT" ) )            {                cmd[0] = "cmd.exe" ;                cmd[1] = "/C" ;                cmd[2] = args[0];            }            else if( osName.equals( "Windows 95" ) )            {                cmd[0] = "command.com" ;                cmd[1] = "/C" ;                cmd[2] = args[0];            }                        Runtime rt = Runtime.getRuntime();            System.out.println("Execing " + cmd[0] + " " + cmd[1]                                + " " + cmd[2]);            Process proc = rt.exec(cmd);            // any error message?            StreamGobbler errorGobbler = new                 StreamGobbler(proc.getErrorStream(), "ERROR");                                    // any output?            StreamGobbler outputGobbler = new                 StreamGobbler(proc.getInputStream(), "OUTPUT");                            // kick them off            errorGobbler.start();            outputGobbler.start();                                                // any error???            int exitVal = proc.waitFor();            System.out.println("ExitValue: " + exitVal);                } catch (Throwable t)          {            t.printStackTrace();          }    }}

使用dir命令,运行GoodWindowsExec产生:
E:\classes\com\javaworld\jpitfalls\article2>java TestExec "e:\program files\netscape\program\netscape.exe e:\java\docs\index.html"ExitValue: 0


ok!网景浏览器的打开,然后装入Java帮助文档。
一个额外的改进包括一个命令行开关,使TestExec接受从标准输入的输入流。然后您将使用Process.getOutputStream()方法通过输入派生外部程序。

总之,遵循这些法则,以避免的陷阱在运行时执行():

你不能从外部过程获得一个退出状态,直到它已经退出
你必须从你外部程序立即处理输入、输出和错误流
您必须使用运行时exec()来执行程序
你不能使用运行时执行()就像一个命令行



热点排行