※※Java调用Runtime.exec()要注意的问题

※※Java调用Runtime.exec()要注意的问题
标签: exec Java Runtime
字体:【 默认 中 大 】
http://it.superkoo.com/#/topic/479/

最近开发一个项目需要在JAVA中调用VC写的一个EXE程序,首先想到的方法肯定是用Runtime.exec(),但一写就发现,事情并没有那么的简单。后来经过多番调试与查找资料才明白了其中的细节:

(1)等待命令执行结束用waitFor(),其返回值就是命令的返回值
(2)如果出现程序执行被挂起,没有任何反应的情况,是由于没有读取命令子进程的正常输出流或错误输出流导致缓冲区被占满,进程被锁住。这个时候需要把输出流中的内容给读出来。最好的做法是使用两个线程,分别同时读取正常输出流和错误输出流。
(3)执行Windows平台上的命令时使用cmd.exe /C,如cmd.exe /C dir。
(4)记得关闭命令子进程的输入流,通过Process.getOutputStream().close(),这样不会导致命令子进程被锁住。

1、Runtime.exec的用法

The class java.lang.Runtime features a static method called getRuntime(), which retrieves the current Java Runtime Environment. This is the only way to obtain a reference to the Runtime Object. With this reference you can run external programs by invoking the Runtime class’s exec() method. Developers often call this method to launch a browser for displaying a help page in HTML.

There are four overloaded version of the exec() command:

Public Process exec(String command);

Public Process exec(String[] cmdArray);

Public Process exec(String command, String []envp);

Public Process exec(String [] cmdArray, String []envp);

For each of these methods, a command—and possible and possibly a set of arguments—is passed to an operating system function call. This subsequently creates an operating system specific process(a running program )with a reference to a Process class returned to the Java VM. The Process is a abstract class ,because a specific subclass of Process exists for each operating system.

You can pass three possible input params into to these methods:

1.a single string that represents both the program to execute and any args to the program.

2.an array of strings that separate the program from its argumen

3.an array of the environment variables.

Pass the environment variables in the form name=value. If you use the version of exec() with a sigle string for both the program and its arguments, note that the string is parsed using white space as the delimiter via the StringTokenizer class.

The first pitfall relating to Runtime.exec() is the IllegalThreadStateException the prevalent first test of an API is to code its most obvious methods.To see the value the external process returns we use the exitValue() method on the Process class. In our first example, we will attempt to execute the Java compliere(javac.exe)

2、一个错误的程序示例

java 代码
   
   
  1. import java.util.*;
  2. import java.io.*;
  3. public class BadExecJavac
  4. {
  5.     public static void main(String args[])
  6.     {
  7.         try
  8.         {           
  9.             Runtime rt = Runtime.getRuntime();
  10.             Process proc = rt.exec("javac");
  11.             int exitVal = proc.exitValue();
  12.             System.out.println("Process exitValue: " + exitVal);
  13.         } catch (Throwable t)
  14.           {
  15.             t.printStackTrace();
  16.           }
  17.     }
  18. }

运行结果如下:

E:\classes\com\javaworld\jpitfalls\article2>java BadExecJavac

java.lang.IllegalThreadStateException: process has not exited

         at java.lang.Win32Process.exitValue(Native Method)

         at BadExecJavac.main(BadExecJavac.java:13)

这是因为当进程还没有结束的情况下,调用exitValue方法会抛出IllegalThreadStateException.当然了我们会问为什吗这个方法不会等到进程结束在返回一个合理的值?

在检查Process类的所有可用方法以后我们发现WairFor()是一个更合适的方法。事实上waitFor也会返回exit value。这意味着你不可以同时用exitvalue和waitfor,而是只能选择一个。

当然了也有情况你要在waitfor之前用exitvalue方法:就是你不想因为外部程序永远无法完成而一直等待下去。

因此为了避免这个陷阱,我们要么捕获IllegalThreadStateException异常,要么等待进程完成。我们相当然的以为可以用waitfor来等待程序的结束。代码如下:

java 代码
   
   
  1. import java.util.*;
  2. import java.io.*;
  3. public class BadExecJavac2
  4. {
  5.      public static void main(String args[])
  6.      {
  7.          try
  8.          {             
  9.             Runtime rt = Runtime.getRuntime();
  10.              Process proc = rt.exec("javac");
  11.              int exitVal = proc.waitFor();
  12.              System.out.println("Process exitValue: " + exitVal);
  13.          } catch (Throwable t)
  14.            {
  15.              t.printStackTrace();
  16.            }
  17.      }
  18. }

这次在linux下面返回的结果是2,而在windows下面据说程序会挂起,关于其原因我们可以在jdk文档中找到部分解释:因为一些操作系统为标准的输入输出仅仅提供有限的缓冲区,当不能正确的将信息写进输入流或者从输出流中获取信息时,就会导致子进程的阻塞,甚至死锁。

3、一个平庸的解决方案

现在我们就根据jdk文档来处理javac进程的输出,当你不带任何参数运行javac时,它会打印出一系列的有用的提示信息。而这些会被传送到stderr流中。我们可以写程序在其返回前获取这些信息。下面的代码提供了一个平庸的解决方案。

java 代码
    
    
  1. import java.util.*;
  2. import java.io.*;
  3. public class MediocreExecJavac
  4. {
  5.      public static void main(String args[])
  6.      {
  7.          try
  8.          {             
  9.             Runtime rt = Runtime.getRuntime();
  10.              Process proc = rt.exec("javac");
  11.              InputStream stderr = proc.getErrorStream();
  12.              InputStreamReader isr = new InputStreamReader(stderr);
  13.              BufferedReader br = new BufferedReader(isr);
  14.              String line = null;
  15.              System.out.println("<ERROR>");
  16.              while ( (line = br.readLine()) != null)
  17.                  System.out.println(line);
  18.              System.out.println("</ERROR>");
  19.              int exitVal = proc.waitFor();
  20.              System.out.println("Process exitValue: " + exitVal);
  21.          } catch (Throwable t)
  22.            {
  23.              t.printStackTrace();
  24.            }
  25.      }
  26. }

这次程序可以正确的输出了提示信息,但是我们应该注意到其返回代码是2,我们知道任何非0的返回代码都表示程序不正常。所以我们需要进一步的查找原因。对于win32而言是file not found,很明显javac期望我们提供编译的文件。所以对于永远挂起的问题,如果你运行的程序会有输出或者要求输出入时,你需要处理输出和输入。

3、不要把命令当成一个可以执行的程序

很多新手把一些windows的命令当中可以独立运行的程序因此他们就陷入了Runtime的另一个陷阱,如下面的例子所示:

java 代码
    
    
  1. import java.util.*;
  2. import java.io.*;
  3. public class BadExecWinDir
  4. {
  5.      public static void main(String args[])
  6.      {
  7.          try
  8.          {             
  9.             Runtime rt = Runtime.getRuntime();
  10.              Process proc = rt.exec("dir");
  11.              InputStream stdin = proc.getInputStream();
  12.              InputStreamReader isr = new InputStreamReader(stdin);
  13.              BufferedReader br = new BufferedReader(isr);
  14.              String line = null;
  15.              System.out.println("<OUTPUT>");
  16.              while ( (line = br.readLine()) != null)
  17.                  System.out.println(line);
  18.              System.out.println("</OUTPUT>");
  19.              int exitVal = proc.waitFor();             
  20.             System.out.println("Process exitValue: " + exitVal);
  21.          } catch (Throwable t)
  22.            {
  23.              t.printStackTrace();
  24.            }
  25.      }
  26. }

据说在windows下面会抛出如下的异常:

java.io.IOException: CreateProcess: dir error=2

         at java.lang.Win32Process.create(Native Method)

         at java.lang.Win32Process.<init>(Unknown Source)

         at java.lang.Runtime.execInternal(Native Method)

         at java.lang.Runtime.exec(Unknown Source)

         at java.lang.Runtime.exec(Unknown Source)

         at java.lang.Runtime.exec(Unknown Source)

         at java.lang.Runtime.exec(Unknown Source)

         at BadExecWinDir.main(BadExecWinDir.java:12)

我在linux下面运行的结果是正确的。前面说了在win32下面2代表是文件没有找到,而在这种情况下表明是dir.exe没有找到,(因为根本就没有这个文件,他们都被封装到common.com (win95)或者cmd.exe中了。

下面我们列出一个正确的处理Process的输入输出流的方法。需要用一个线程类。

java 代码
    
    
  1. import java.util.*;
  2. import java.io.*;
  3. class StreamGobbler extends Thread
  4. {
  5.      InputStream is;
  6.      String type;
  7.     StreamGobbler(InputStream is, String type)
  8.      {
  9.          this.is = is;
  10.          this.type = type;
  11.      }
  12.      
  13.     public void run()
  14.      {
  15.          try
  16.          {
  17.              InputStreamReader isr = new InputStreamReader(is);
  18.              BufferedReader br = new BufferedReader(isr);
  19.              String line=null;
  20.              while ( (line = br.readLine()) != null)
  21.                  System.out.println(type + ">" + line);     
  22.             } catch (IOException ioe)
  23.                {
  24.                  ioe.printStackTrace();  
  25.               }
  26.      }
  27. }

用于专门的处理输入输出。

java 代码
    
    
  1. public class GoodWindowsExec
  2. {
  3.      public static void main(String args[])
  4.      {
  5.          if (args.length < 1)
  6.          {
  7.              System.out.println("USAGE: java GoodWindowsExec <cmd>");
  8.              System.exit(1);
  9.          }
  10.          
  11.         try
  12.          {             
  13.             String osName = System.getProperty("os.name" );
  14.              String[] cmd = new String[3];
  15.              if( osName.equals( "Windows NT" ) )
  16.              {
  17.                  cmd[0] = "cmd.exe" ;
  18.                  cmd[1] = "/C" ;
  19.                  cmd[2] = args[0];
  20.              }
  21.              else if( osName.equals( "Windows 95" ) )
  22.              {
  23.                  cmd[0] = "command.com" ;
  24.                  cmd[1] = "/C" ;
  25.                  cmd[2] = args[0];
  26.              }
  27.              
  28.             Runtime rt = Runtime.getRuntime();
  29.              System.out.println("Execing " + cmd[0] + " " + cmd[1
  30.                                + " " + cmd[2]);
  31.              Process proc = rt.exec(cmd);
  32.              // any error message?
  33.              StreamGobbler errorGobbler = new 
  34.                 StreamGobbler(proc.getErrorStream(), "ERROR");             
  35.             
  36.             // any output?
  37.              StreamGobbler outputGobbler = new 
  38.                 StreamGobbler(proc.getInputStream(), "OUTPUT");
  39.                  
  40.             // kick them off
  41.              errorGobbler.start();
  42.              outputGobbler.start();
  43.                                      
  44.             // any error???
  45.              int exitVal = proc.waitFor();
  46.              System.out.println("ExitValue: " + exitVal);         
  47.         } catch (Throwable t)
  48.            {
  49.              t.printStackTrace();
  50.            }
  51.      }
  52. }

如果运行如下命令上面的代码会调用word程序

>java GoodWindowExec “abc.doc”

也就是说文件类型如果window能够识别它就会调用对应的程序处理。

StreamGlobbler的最重要作用是他会清空所有的传递给他的inputstream,这样不会造成Process阻塞或者死锁。

另外一种实现方式:

java 代码
    
    
  1. package cmd; 
  2.  
  3. import java.io.BufferedReader
  4. import java.io.InputStream
  5. import java.io.InputStreamReader
  6.  
  7. class StreamDrainer implements Runnable
  8.     private InputStream ins; 
  9.  
  10.     public StreamDrainer(InputStream ins) { 
  11.         this.ins = ins; 
  12.     } 
  13.  
  14.     public void run() { 
  15.         try
  16.             BufferedReader reader = new BufferedReader
  17.                     new InputStreamReader(ins)); 
  18.             String line = null
  19.             while ((line = reader.readLine()) != null) { 
  20.                 System.out.println(line); 
  21.             } 
  22.         } catch (Exception e) { 
  23.             e.printStackTrace(); 
  24.         } 
  25.     } 
  26.  
  27.  
  28. public class TestRunCmd { 
  29.  
  30.     public static void main(String[] args) { 
  31.         String[] cmd = new String[] { "cmd.exe", "/C", "wmic process get name" }; 
  32.         try
  33.             Process process = Runtime.getRuntime().exec(cmd); 
  34.              
  35.             new Thread(new StreamDrainer(process.getInputStream())).start(); 
  36.             new Thread(new StreamDrainer(process.getErrorStream())).start(); 
  37.              
  38.             process.getOutputStream().close(); 
  39.  
  40.             int exitValue = process.waitFor(); 
  41.             System.out.println("返回值:" + exitValue); 
  42.         } catch (Exception e) { 
  43.             e.printStackTrace(); 
  44.         } 
  45.  
  46.     } 
  47.  

4、不要把exec当成命令行,它不会重定向文件

如你在linux下面type ls> abc.txt或者在window下面dir>abc.txt 会把输出的结果通过管道重定向到文件中。但是exec不会。你必须自己写程序来处理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值