【问题标题】:Running jar with Processbuilder doesn't work properly使用 Processbuilder 运行 jar 无法正常工作
【发布时间】:2016-06-23 14:03:05
【问题描述】:

我有以下代码:

ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();

当使用这个命令从我的终端运行 jar 文件时:

java -jar test.jar 135 3 3 appName

然后它就像一个魅力。 jar 将一些东西推送到我的数据库中,所以我看到它正在工作。但是,当使用上面提到的 processBuilder 代码从我的 JavaServlet 执行此操作时,我的数据库中没有任何数据,也没有任何错误。

不管进程本身正在运行,我在终端中使用“ps ax”检查了它。所以我想知道这里的区别在哪里?我做错了什么?

有人有想法吗?

编辑:更多代码:

ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();

System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );

InputStream in = proc.getInputStream();
InputStream err = proc.getErrorStream();

byte result[] = new byte[ in.available() ];
in.read( result, 0, result.length );
System.out.println( new String( result ) );

byte error[] = new byte[ err.available() ];
err.read( error, 0, error.length );
System.out.println( new String( error ) );

更新:

我试图调用一个 shell 脚本而不是我的 jar。所以我用我的 java 文件中的 processbuilder 调用了一个 shell 脚本。

我的 shell 脚本是这样做的:

java -jar test.jar "$1" "$2" "$3" "$4"

好吧,它仍然没有工作。所以我尝试了这个:

gnome-terminal -x java -jar test.jar "$1" "$2" "$3" "$4"

突然间它起作用了! 但是它会打开执行 jar 文件的 gnome-terminal。

所以我想知道,这是否与 eclipse 中未显示的输出有关?我真的不明白。现在这是一个很好的解决方法。但我真的很想在每次执行 jar 时都不打开终端的情况下让它工作。

【问题讨论】:

  • 过程中是否有任何错误/异常?
  • 尝试给出java的完整路径。
  • @MickaëlB:不,很遗憾没有。
  • @PeterMmm:我也尝试过,但结果相同。或者等等......你对java是什么意思。你的意思是 jar 文件或 java jre 的完整路径?
  • @PeterMmm:我现在尝试了这个:/usr/lib64/jvm/java-7-openjdk/bin/java -jar....但这也不起作用。

标签: java jar processbuilder


【解决方案1】:

首先,我无法重现您的问题,因此此答案将仅基于文档。

默认情况下,创建的子进程没有自己的终端或 安慰。它的所有标准 I/O(即标准输入、标准输出、标准错误)操作 将被重定向到可以访问它们的父进程 通过使用 getOutputStream() 方法获得的流, getInputStream() 和 getErrorStream()。父进程使用这些 流向子流程提供输入并从子流程中获取输出。 因为 一些原生平台只为标准提供有限的缓冲区大小 输入输出流,未能及时写入输入流 或者读取子进程的输出流可能会导致子进程 阻塞,甚至死锁。

java.lang.Process Documentation

基本上,这告诉您需要正确处理外部进程的流,否则在某些平台上可能会导致死锁。这意味着如果我们运行的命令产生了一些输出,您必须读取该输出。

让我们看看你的代码;您正在调用 process.waitFor() 以等待进程完成,但问题是您的 进程无法完成 没有您阅读/使用它的输出,因此您正在创建死锁。

如何克服这个问题:

  1. 方法是使用InputStreamConsumerThread 正确处理输入/错误流;

    public class InputStreamConsumerThread extends Thread
    {
      private InputStream is;
      private boolean sysout;
      private StringBuilder output = new StringBuilder();
    
      public InputStreamConsumerThread (InputStream is, boolean sysout)
      {
        this.is=is;
        this.sysout=sysout;
      }
    
      public void run()
      {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(is)))
        {
          for (String line = br.readLine(); line != null; line = br.readLine())
          {
            if (sysout)
              System.out.println(line);    
            output.append(line).append("\n");
          }
        }
      }
      public String getOutput(){
        return output.toString();
      }
    }
    

您的代码将是;

    String systemProperties = "-Dkey=value";    
    ProcessBuilder pb = new ProcessBuilder( "java", systemProperties, "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
    pb.directory( new File("/home/userName/TestBSC") );
    Process proc = pb.start();

    InputStreamConsumerThread inputConsumer = 
         new InputStreamConsumerThread(proc.getInputStream(), true);
    InputStreamConsumerThread errorConsumer = 
         new InputStreamConsumerThread(proc.getErrorStream(), true);

    inputConsumer.start();
    errorConsumer.start();

    System.out.println( "Job running" );
    proc.waitFor(); // wait until jar is finished
    System.out.println( "Job finished" );

    inputConsumer.join(); // wait for consumer threads to read the whole output
    errorConsumer.join();

    String processOutput = inputConsumer.getOutput();
    String processError = errorConsumer.getOutput();
    
    if(!processOutput.isEmpty()){
        //there were some output
    }

    if(!processError.isEmpty()){
        //there were some error
    }

    
  1. 方法是使用ProcessBuilder 来重定向输出。如果您只想让您的子进程使用与父进程相同的输入/输出流,您可以像这样使用ProcessBuilder.Redirect.INHERIT

    ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
    pb.directory( new File("/home/userName/TestBSC") );
    pb.redirectErrorStream(true); // redirect error stream to output stream
    pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
    Process proc = pb.start();
    
    System.out.println( "Job running" );
    //since process builder will handle the streams for us
    //we can call waitFor() safely
    proc.waitFor(); 
    System.out.println( "Job finished" );
    
  2. 方法正在使用第 3 方库。如果您不想麻烦ProcessBuilder 和消费者自己的线程,我知道有 2 个库可以很好地处理创建子进程。

低开销、非阻塞 I/O、外部进程执行 Java的实现。它是一个替代品 java.lang.ProcessBuilder 和 java.lang.Process.

你有没有因为每当你生成一个 在 Java 中处理你必须创建两个或三个“泵”线程(对于 每个进程)从 stdout 和 stderr 管道中提取数据 将数据泵入标准输入? 如果您的代码启动了很多进程,您可以 有几十个或几百个线程除了抽取数据什么都不做。

NuProcess 使用 JNA 库来使用特定于平台的原生 API 在 Java 进程和 衍生进程。

NuProcess

从 爪哇。有 JRE 选项,例如 Runtime.exec() 和 进程生成器。还有 Apache Commons Exec。尽管如此我们 创建了另一个流程库 (YAPL)。

这种疯狂努力的一些原因

改进流的处理读/写流重定向 stderr 到 stdout 改进了超时处理改进了检查 退出代码 改进的 API One 用于相当复杂的用例的内衬 一个 衬管将流程输出转换为对流程的字符串访问 对象可用支持异步进程(未来)改进 使用 SLF4J API 进行日志记录 支持多个进程

ZT Process Executor

Java 的 Process API 中还有其他陷阱,请查看这篇 JavaWorld 文章了解更多信息When Runtime.exec() won't

【讨论】:

  • 哇,感谢您的出色回答。我目前正在测试方法 1。是否有可能,我需要在 InputStreamConsumerThread 的运行方法中执行 new BufferedReader() 而不是 new BufferedInputStream()?因为它说,我无法从 BufferedInputStream 转换为 BufferedReader。以及如何从 errorConsumer 中读取错误等?
  • @progNewbie 是的,我的错。更正了答案
  • 谢谢。如何从 errorConsumer 中读取信息?因为在我的原始代码中,我在 jar 完成执行后检查是否有错误。
  • @progNewbie 添加了从InputStreamConsumerThread 获取输出的功能。
  • 哇,谢谢!虽然需要processOutput.isEmpty()。
【解决方案2】:

你可以试试这个吗?

更新

代码:

// Java runtime
Runtime runtime = Runtime.getRuntime();
// Command
String[] command = {"java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application};
// Process
Process process = runtime.exec(command, null, new File("/home/userName/TestBSC"));

【讨论】:

  • 不幸的是,它发生了同样的情况。该过程被执行。但是没有输出,也没有任何内容存储在我的数据库中。就像进程不是真正的处理。这太奇怪了。
  • 进程的exitValue是什么? (docs.oracle.com/javase/7/docs/api/java/lang/…)
  • 进程不会停止。 :(
  • 这是优先事项还是类似的事情?
  • 我不这么认为。你还有这个waitFor() 吗?如果是的话,你能把它移到System.out.println( new String( error ) );之后吗?
猜你喜欢
  • 2016-02-08
  • 2018-03-25
  • 1970-01-01
  • 2014-08-25
  • 1970-01-01
  • 2022-06-14
  • 1970-01-01
  • 2018-09-24
  • 1970-01-01
相关资源
最近更新 更多