53

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

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

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

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


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

EXECUTE.application( CLASS_TO_BE_EXECUTED );

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

4

9 回答 9

79

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

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

于 2009-04-07T01:53:33.810 回答
46

两个提示:

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

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

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

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

于 2009-03-12T02:26:45.043 回答
6

扩展@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);
    }
}
于 2016-02-08T17:40:07.367 回答
5

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

于 2009-04-12T05:44:47.370 回答
3

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

于 2009-03-12T00:26:55.427 回答
3
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;
    }
}
于 2009-04-05T10:15:41.093 回答
3

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

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

于 2009-04-06T20:19:53.333 回答
2

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

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

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

于 2009-04-06T16:01:11.613 回答
1

当您从 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 扩展名的临时文件,然后将带有空格的命令放在那里并运行 bat。但是,通过 start 运行 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 ).
于 2009-11-03T07:45:49.027 回答