web-dev-qa-db-ja.com

Javaアプリケーションを別のプロセスで実行する

Javaアプリケーションは、場所とは対照的に、プラットフォームに依存しない方法で、名前を使用して別のプロセスにロードできますか?

私はあなたが経由してプログラムを実行できることを知っています...

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

...このメソッドの主な問題は、そのような呼び出しがプラットフォーム固有であることです。


理想的には、メソッドを次のような単純なものにラップします...

EXECUTE.application( CLASS_TO_BE_EXECUTED );

...アプリケーションクラスの完全修飾名をCLASS_TO_BE_EXECUTEDとして渡します。

42
Ande TURNER

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)

41
stepancheg

これは、提供されている他の回答の一部を統合したものです。 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);

これが機能するにはクラスがクラスパスにある必要があるため、名前の文字列表現ではなく実際のクラスを渡すのが理にかなっていると思いました。

64
hallidave

@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);
    }
}
6
andrej

これはあなたにとってはやり過ぎかもしれませんが、 Project Akuma はあなたが望むことなどを行います。私は このエントリ を介して、Kosuke(Sunのロックスタートプログラマーの1人)の非常に有用なブログで見つけました。

4
StaxMan

本当にネイティブに起動する必要がありますか? 「メイン」メソッドを直接呼び出すことはできますか? mainについての唯一の特別なことは、VMランチャーがそれを呼び出すことです。あなた自身がmainを呼び出すことを止めるものは何もありません。

3
TofuBeer

ProcessBuilder APIをチェックアウトしましたか? 1.5以降で利用可能

http://Java.Sun.com/javase/6/docs/api/Java/lang/ProcessBuilder.html

3
lothar
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;
    }
}
3
Ande TURNER

これを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を見つけることができません」という苦情を開始します。

「Program Files」のスペースをバックスラッシュでエスケープすることもできません。したがって、アイデアはスペースを使用しないことです。 bat拡張子を持つ一時ファイルを生成し、そこにスペースを入れてコマンドを入力し、batを実行します。ただし、開始からバットを実行しても、終了しても終了しないため、バッチファイルの最後に「exit」を配置する必要があります。

これはまだ不機嫌そうです。

そのため、代替手段を探して、「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 ).
1
Matiaan

TofuBeerが言わなければならなかったことに続きます。本当に別のJVMを分岐する必要があるのですか? JVMは最近、並行処理を非常によくサポートしているので、新しいスレッドを1つまたは2つスピンオフするだけで、比較的安価で多くの機能を取得できます(Foo#main(String [])への呼び出しが必要な場合とそうでない場合があります)。詳細については、Java.util.concurrentをご覧ください。

分岐することにした場合、必要なリソースの検索に関連する複雑さを少し設定します。つまり、アプリが頻繁に変更され、多数のjarファイルに依存している場合は、それらをすべて追跡してクラスパス引数に渡すことができるようにする必要があります。さらに、このようなアプローチでは、(現在実行中の)JVMの場所(正確ではない可能性があります)と現在のクラスパスの場所(スポーンの方法によっては正確である可能性はさらに低い)の両方を推測する必要がありますスレッドが呼び出されました-jar、jnlp、展開された.classes dir、コンテナなど。

一方、静的な#mainメソッドへのリンクには落とし穴もあります。静的修飾子は、他のコードに漏れるという厄介な傾向があり、一般的にデザイン志向の人々によって嫌われています。

1
jasonnerothin