在单独的进程中执行Java应用程序

在单独的进程中执行Java应用程序,java,process,exec,Java,Process,Exec,Java应用程序能否以独立于平台的方式,使用其名称(而不是位置)在单独的进程中加载 我知道你可以通过 Process process = Runtime.getRuntime().exec( COMMAND ); 。。。这种方法的主要问题是这样的调用是特定于平台的 理想情况下,我会将一个方法包装成像 EXECUTE.application( CLASS_TO_BE_EXECUTED ); 。。。并将应用程序类的完全限定名传递为要执行的类您真的必须以本机方式启动它们吗?你能直接调用他们的“主

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

我知道你可以通过

Process process = Runtime.getRuntime().exec( COMMAND );
。。。这种方法的主要问题是这样的调用是特定于平台的


理想情况下,我会将一个方法包装成像

EXECUTE.application( CLASS_TO_BE_EXECUTED );

。。。并将应用程序类的完全限定名传递为
要执行的类

您真的必须以本机方式启动它们吗?你能直接调用他们的“主要”方法吗?main的唯一特殊之处是VM启动器调用它,没有任何东西可以阻止您自己调用main。

两个提示:

System.getProperty(“java.home”)+“/bin/java”
提供了指向java可执行文件的路径

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

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


Process.exec(javaExecutable,“-classpath”,url.join(“:”),CLASS\u TO\u BE\u EXECUTED)

下面是TofuBeer说过的话:你确定你真的需要另一个JVM吗?现在JVM对并发性有很好的支持,因此您可以通过拆分一两个新线程(可能需要也可能不需要调用Foo#main(String[]),以相对便宜的价格获得很多功能。有关更多信息,请查看java.util.concurrent

如果您决定使用fork,那么您将面临与查找所需资源相关的一点复杂性。也就是说,如果您的应用程序经常更改,并且依赖于一堆jar文件,那么您需要跟踪它们,以便将它们传递给classpath参数。此外,这种方法需要推断(当前正在执行的)JVM的位置(可能不准确)和当前类路径的位置(更不可能准确,这取决于调用生成线程的方式—jar、jnlp、exploded.classes dir、某些容器等)


另一方面,链接到静态的主要方法也有其缺陷。静态修饰符有泄漏到其他代码中的严重趋势,并且通常受到设计人员的反对。

您是否查阅了ProcessBuilderAPI?从1.5开始提供


这是对已经提供的一些其他答案的综合。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();
    }

}

我认为传入实际的类而不是名称的字符串表示是有意义的,因为无论如何,类必须在类路径中才能起作用。

这对您来说可能有点过分,但可以满足您的需要。
我在Kohsuke(Sun的rock start程序员之一)的博客上发现了它,非常有用。

从java GUI运行它时出现的一个问题是它在后台运行。 因此,您根本看不到命令提示

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

但是,“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扩展名的临时文件,然后在其中放置带有空格的命令 然后击球。 但是,通过启动运行bat,完成后不会退出, 因此,您必须在批处理文件的末尾添加“exit”

这看起来还是很恶心

因此,在寻找替代方案时,我发现在“程序文件”的空间中使用引号空间quote实际上可以与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 ).

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


如果我没弄错的话,你有几个带有main()方法的类,你想在不同的进程中启动它们吗?如果你执行(“java.exe”,CLASS_to_BE_EXECUTED.CLASS.getName())怎么样?如何从用户那里获取java类的输入,java类作为一个由java程序自己启动的进程运行,使用类似于br.readLine()的东西我正在与OP合作,如果我们可以绕过整个CLI接口,那就太好了。有人真的应该想出一个包装类来实现这一点,这样应用程序开发人员就可以专注于业务逻辑。第三个提示:如果您想独立于平台,就不要使用URL.join(“:”).File.some_常量而不是:(您可以查找some_常量:-)URL方法会从jnlp文件的代码库中为我提供jar文件的URL。即。那对我不起作用。虽然是本地的,但它确实存在。@Jean请更正“伪代码”:如果您不熟悉这些类,则很难获得语法正确性。随后将涉及RMI,这些进程充当主“内核”进程的守护进程,他们可能不在同一台计算机上,而是在网络的其他地方。我认为原来的措辞对更多的读者来说是有用的。看起来它被转移到了Github。考虑到Oracle对Jenkins/Hudson事件的不满,这是有道理的。如果您想启动某种服务器,可能需要在process builder(在
start()
之前)上使用
redirectOutput(…)
redirectError(…)
int status = JavaProcess.exec(MyClass.class, args);
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 ).
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);
    }
}