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

时间:2009-03-11 21:06:03

标签: java process exec

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

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

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

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


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

EXECUTE.application( CLASS_TO_BE_EXECUTED );

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

9 个答案:

答案 0 :(得分:64)

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

public final class JavaProcess {

    private JavaProcess() {}        

    public static int exec(Class klass) 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();

        ProcessBuilder builder = new ProcessBuilder(
                javaBin, "-cp", classpath, className);

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

}

您可以像这样运行此方法:

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

我认为传递实际的类而不是名称的String表示是有意义的,因为类必须在类路径中才能使其工作。

答案 1 :(得分:41)

两个提示:

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

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

然后你的EXECUTE.application只是(伪代码):

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

答案 2 :(得分:5)

扩展@ 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);
    }
}

答案 3 :(得分:4)

这对你来说可能是一种矫枉过正,但Project Akuma可以满足您的需求。 我在Kohsuke's(Sun的摇滚开始程序员之一)通过this entry找到了它,这是非常有用的博客。

答案 4 :(得分:3)

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

答案 5 :(得分: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;
    }
}

答案 6 :(得分:3)

您是否查看了ProcessBuilder API?自1.5以来可用。

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

答案 7 :(得分:1)

关注TofuBeer所说的话:你确定你真的需要分叉另一个JVM吗? JVM现在对并发性有很好的支持,所以只需关闭一个或两个新线程(可能需要也可能不需要调用Foo#main(String [])),你可以相对便宜地获得很多功能。查看java.util.concurrent了解更多信息。

如果您决定进行分叉,那么您需要为自己找到与查找所需资源相关的一些复杂性。也就是说,如果你的应用程序频繁更改并依赖于一堆jar文件,你需要跟踪它们,以便它们可以传递给类路径arg。此外,这种方法需要推断(当前正在执行的)JVM的位置(可能不准确)和当前类路径的位置(根据产生的方式,它更不可能准确)线程已被调用 - jar,jnlp,爆炸.classes dir,某些容器等)。

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

答案 8 :(得分:1)

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

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

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

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

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

这似乎仍然令人讨厌。

因此,在寻找替代方案时,我发现在“Program Files”空间中使用引用空间引用实际上与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 ).