【问题标题】:Executing a Java application in a separate process在单独的进程中执行 Java 应用程序
【发布时间】:2010-10-12 18:58:00
【问题描述】:

Java 应用程序能否以独立于平台的方式使用其名称而不是其位置加载到单独的进程中?

我知道你可以通过...执行程序

Process process = Runtime.getRuntime().exec( COMMAND );

...此方法的主要问题是此类调用是特定于平台的。


理想情况下,我会将一个方法包装成一个简单的东西......

EXECUTE.application( CLASS_TO_BE_EXECUTED );

...并将应用程序类的完全限定名称作为CLASS_TO_BE_EXECUTED 传递。

【问题讨论】:

  • 如果我没猜错的话,你有几个带有 main() 方法的类,你想在不同的进程中启动它们?
  • 如果你 exec("java.exe", CLASS_TO_BE_EXECUTED.class.getName()) 怎么样?
  • 如何获取用户输入的 java 类作为由 java 程序自行启动的进程运行,使用类似 br.readLine()
  • 我支持 OP,如果我们能绕过整个 CLI 界面就好了。确实应该有人想出一个包装类来做到这一点,以便应用程序开发人员可以专注于业务逻辑。

标签: java process exec


【解决方案1】:

这是对已提供的其他一些答案的综合。 Java 系统属性提供了足够的信息来得出 java 命令的路径和类路径,我认为这是一种独立于平台的方式。

public final class JavaProcess {

    private JavaProcess() {}        

    public static int exec(Class klass, List<String> args) throws IOException,
                                               InterruptedException {
        String javaHome = System.getProperty("java.home");
        String javaBin = javaHome +
                File.separator + "bin" +
                File.separator + "java";
        String classpath = System.getProperty("java.class.path");
        String className = klass.getName();

        List<String> command = new LinkedList<String>();
        command.add(javaBin);
        command.add("-cp");
        command.add(classpath);
        command.add(className);
        if (args != null) {
            command.addAll(args);
        }

        ProcessBuilder builder = new ProcessBuilder(command);

        Process process = builder.inheritIO().start();
        process.waitFor();
        return process.exitValue();
    }

}

你可以像这样运行这个方法:

int status = JavaProcess.exec(MyClass.class, args);

我认为传递实际类而不是名称的字符串表示形式是有意义的,因为无论如何该类必须在类路径中才能正常工作。

【讨论】:

  • 如果你想启动某种服务器,你可能需要在进程构建器上使用redirectOutput(...)redirectError(...)(在start()之前),否则它可能会挂起并且你可能无法联系它。不知道为什么...
  • 如何以分离模式运行新进程,让外层进程继续工作
  • 谢谢!从实际的类路径中分离出“-cp”会有所帮助。将它们作为单个字符串组合在一起时,会出错“无法识别的选项:-cp
  • 最好使用String className = klass.getName(); 而不是String className = klass.getCanonicalName(); 来正确处理嵌套类。
  • 有谁知道包含 VM 参数的技术,即 ProcessBuilder 的 -D 和 -X ?我已按照 api 文档上的说明使用 processbuilder.environment() 来查看地图,但这些值在被调用程序中似乎不可用 - 即 System.getProperty("xyz", value) 不可用在被调用的程序中,即使我可以看到它被正确设置
【解决方案2】:

两个提示:

System.getProperty("java.home") + "/bin/java" 为您提供 java 可执行文件的路径。

((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURL() 帮助您重构当前应用程序的类路径。

那么你的EXECUTE.application 就是(伪代码):

Process.exec(javaExecutable, "-classpath", urls.join(":"), CLASS_TO_BE_EXECUTED)

【讨论】:

  • 第三个提示:如果你想独立于平台,不要使用 urls.join(":")。
  • File.some_constant 而不是 : (你可以查一下 some_constant :-)
  • URL 方法为我提供了 jnlp 文件代码库中 jar 文件的 URL。即url.com/Jar.jar。这对我不起作用。虽然它是本地的。
  • 请更正“伪代码”:如果您不熟悉这些类,很难在语法上保持正确。
【解决方案3】:

扩展@stepancheg 的答案,实际代码看起来像这样(以测试的形式)。

import org.junit.Test;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.stream.Collectors;

public class SpinningUpAJvmTest {
    @Test
    public void shouldRunAJvm() throws Exception {
        String classpath = Arrays.stream(((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs())
                .map(URL::getFile)
                .collect(Collectors.joining(File.pathSeparator));
        Process process = new ProcessBuilder(
                System.getProperty("java.home") + "/bin/java",
                "-classpath",
                classpath,
                MyMainClass.class.getName()
                // main class arguments go here
        )
                .inheritIO()
                .start();
        int exitCode = process.waitFor();
        System.out.println("process stopped with exitCode " + exitCode);
    }
}

【讨论】:

    【解决方案4】:

    这对您来说可能有点矫枉过正,但Project Akuma 可以满足您的需求,甚至更多。 我通过 Kohsuke(Sun 的摇滚创业程序员之一)的 this entry 发现它非常有用的博客。

    【讨论】:

    • 好像已经移到 Github 上了。有道理,考虑到甲骨文对整个 Jenkins/Hudson 事情的不满。
    【解决方案5】:

    您检查过 ProcessBuilder API 吗?它从 1.5 开始可用

    http://java.sun.com/javase/6/docs/api/java/lang/ProcessBuilder.html

    【讨论】:

      【解决方案6】:
      public abstract class EXECUTE {
      
          private EXECUTE() { /* Procedural Abstract */ }
      
          public static Process application( final String CLASS_TO_BE_EXECUTED ) {
      
              final String EXEC_ARGUMENT 
              = new StringBuilder().
                    append( java.lang.System.getProperty( "java.home" ) ).
                    append( java.io.File.separator ).
                    append( "bin" ).
                    append( java.io.File.separator ).
                    append( "java" ).
                    append( " " ).
                    append( new java.io.File( "." ).getAbsolutePath() ).
                    append( java.io.File.separator ).
                    append( CLASS_TO_BE_EXECUTED ).
                    toString();
      
              try {       
      
                  return Runtime.getRuntime().exec( EXEC_ARGUMENT );
      
              } catch ( final Exception EXCEPTION ) {     
      
                  System.err.println( EXCEPTION.getStackTrace() );
              }
      
              return null;
          }
      }
      

      【讨论】:

        【解决方案7】:

        您真的必须在本地启动它们吗?你能直接调用他们的“主要”方法吗? main 唯一的特别之处是 VM 启动器调用它,没有什么能阻止你自己调用 main。

        【讨论】:

          【解决方案8】:

          按照 TofuBeer 的说法:你确定你真的需要分叉另一个 JVM 吗?如今,JVM 对并发性的支持非常好,因此您可以通过分离一两个新线程(可能需要也可能不需要调用 Foo#main(String[]))以相对便宜的价格获得很多功能。查看 java.util.concurrent 了解更多信息。

          如果你决定分叉,你就会为寻找所需资源的一些复杂性设置自己。也就是说,如果您的应用程序经常更改并且依赖于一堆 jar 文件,则您需要跟踪它们,以便将它们传递给类路径 arg。此外,这种方法需要推断(当前执行的)JVM 的位置(可能不准确)和当前类路径的位置(更不可能准确,具体取决于生成的方式)线程已被调用 - jar、jnlp、爆炸的 .classes 目录、一些容器等)。

          另一方面,链接到静态#main 方法也有其缺陷。静态修饰符有泄漏到其他代码中的令人讨厌的趋势,并且通常会被具有设计意识的人所反对。

          【讨论】:

          • 随后将涉及 RMI,这些进程充当主“内核”进程的守护进程,并且它们可能不在同一台计算机上,而是在网络上的其他地方。不过,我认为原始措辞对更多读者有用。
          【解决方案9】:

          当您从 java GUI 运行它时出现的一个问题是它在后台运行。 所以你根本看不到命令​​提示符。

          要解决这个问题,您必须通过“cmd.exe”和“start”运行 java.exe。 我不知道为什么,但是如果你把“cmd /c start”放在前面,它会在运行时显示命令提示符。

          但是,“开始”的问题是,如果应用程序的路径中有空格 (java exe的路径通常具有 C:\Program Files\Java\jre6\bin\java.exe 或类似), 然后启动失败并显示“找不到 c:\Program”

          所以你必须在 C:\Program Files\Java\jre6\bin\java.exe 周围加上引号 现在开始抱怨您传递给 java.exe 的参数: "系统找不到文件-cp。"

          用反斜杠转义“程序文件”中的空格也不起作用。 所以这个想法是不使用空间。 生成一个带有 bat 扩展名的临时文件,然后将您的命令与空格放在其中 并运行蝙蝠。 但是,通过启动运行蝙蝠,完成后不会退出, 所以你必须把“exit”放在批处理文件的末尾。

          这看起来还是很糟糕。

          所以,在寻找替代方案时,我发现在“程序文件”的空间中使用引号空格引号实际上可以与 start 一起使用。

          在上面的 EXECUTE 类中,将字符串生成器附加到:

          append( "cmd /C start \"Some title\" " ).
          append( java.lang.System.getProperty( "java.home" ).replaceAll(" ", "\" \"") ).
          append( java.io.File.separator ).
          append( "bin" ).
          append( java.io.File.separator ).
          append( "java" ).
          append( " " ).
          append( new java.io.File( "." ).getAbsolutePath() ).
          append( java.io.File.separator ).
          append( CLASS_TO_BE_EXECUTED ).
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-25
            • 1970-01-01
            • 1970-01-01
            • 2013-01-19
            • 2011-09-05
            • 1970-01-01
            • 2017-11-07
            • 2015-05-20
            相关资源
            最近更新 更多