web-dev-qa-db-fra.com

Exécution d'une application Java dans un processus distinct

Une application Java Java peut-elle être chargée dans un processus distinct en utilisant son nom, par opposition à son emplacement, de manière indépendante de la plate-forme?

Je sais que vous pouvez exécuter un programme via ...

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

... le principal problème de cette méthode est que ces appels sont alors spécifiques à la plate-forme.


Idéalement, j'envelopperais une méthode dans quelque chose d'aussi simple que ...

EXECUTE.application( CLASS_TO_BE_EXECUTED );

... et passez le nom complet d'une classe d'application en tant que CLASS_TO_BE_EXECUTED.

42
Ande TURNER

Deux indices:

System.getProperty("Java.home") + "/bin/Java" vous donne un chemin vers l'exécutable Java.

((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURL() Vous aide à reconstruire le chemin de classe de l'application actuelle.

Alors votre EXECUTE.application Est juste (pseudocode):

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

41
stepancheg

Ceci est une synthèse de certaines des autres réponses qui ont été fournies. Les propriétés système Java fournissent suffisamment d'informations pour trouver le chemin d'accès à la commande Java et le chemin de classe dans ce qui, je pense, est un moyen indépendant de la plate-forme).

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();
    }

}

Vous exécuteriez cette méthode comme suit:

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

J'ai pensé qu'il était logique de passer dans la classe réelle plutôt que dans la représentation String du nom puisque la classe doit de toute façon être dans le chemin de classe pour que cela fonctionne.

64
hallidave

En développant la réponse de @ stepancheg, le code réel ressemblerait à cela (sous la forme d'un test).

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

Cela peut être exagéré pour vous, mais Project Akuma fait ce que vous voulez et plus encore. Je l'ai trouvé via cette entrée sur le blog fabuleusement utile de Kohsuke (l'un des programmeurs rock start de Sun).

4
StaxMan

Devez-vous vraiment les lancer nativement? Pourriez-vous simplement appeler leurs méthodes "principales" directement? La seule chose spéciale à propos de main est que le lanceur VM l'appelle, rien ne vous empêche d'appeler main vous-même.

3
TofuBeer

Avez-vous consulté l'API ProcessBuilder? Il est disponible depuis la 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

Un problème qui se produit lorsque vous l'exécutez à partir d'une interface graphique Java est qu'il s'exécute en arrière-plan. Vous ne pouvez donc pas voir du tout l'invite de commande.

Pour contourner ce problème, vous devez exécuter Java.exe via "cmd.exe" ET "démarrer". Je ne sais pas pourquoi, mais si vous mettez "cmd/c start" en face, il affiche l'invite de commande lors de son exécution.

Cependant, le problème avec "start" est que s'il y a un espace dans le chemin d'accès à l'application (que le chemin d'accès à l'exe Java a généralement comme dans C:\Program Files\Java\jre6\bin\Java.exe ou similaire), puis le démarrage échoue avec "ne peut pas trouver c:\Program"

Vous devez donc mettre des guillemets autour de C:\Program Files\Java\jre6\bin\Java.exe Maintenant, start se plaint des paramètres que vous passez à Java.exe: "Le système ne peut pas trouver le fichier -cp."

Échapper à l'espace dans "Program Files" avec une barre oblique inverse ne fonctionne pas non plus. L'idée est donc de ne pas utiliser d'espace. Générez un fichier temporaire avec l'extension bat, puis mettez votre commande avec des espaces dedans et exécutez la batte. Cependant, exécuter une chauve-souris jusqu'au début ne se termine pas une fois terminé, vous devez donc mettre "exit" à la fin du fichier de commandes.

Cela semble toujours dégueu.

Donc, à la recherche d'alternatives, j'ai trouvé que l'utilisation de l'espace de devis dans l'espace de "Program Files" fonctionne réellement avec start.

Dans la classe EXECUTE ci-dessus, le générateur de chaînes s'ajoute à:

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

Suite à ce que TofuBeer avait à dire: Êtes-vous sûr que vous avez vraiment besoin de débuter une autre JVM? La JVM prend vraiment en charge la concurrence simultanée de nos jours, vous pouvez donc obtenir beaucoup de fonctionnalités pour relativement bon marché en créant simplement un nouveau thread ou deux (qui peuvent ou non nécessiter un appel à Foo # main (String [])). Consultez Java.util.concurrent pour plus d'informations.

Si vous décidez de bifurquer, vous vous installez pour un peu de complexité liée à la recherche des ressources requises. Autrement dit, si votre application change fréquemment et dépend d'un tas de fichiers jar, vous devrez tous les suivre afin qu'ils puissent être transmis à l'argument classpath. De plus, une telle approche nécessite de déduire à la fois l'emplacement de la machine virtuelle Java (en cours d'exécution) (qui peut ne pas être précis) et l'emplacement du chemin de classe actuel (qui est encore moins susceptible d'être précis, selon la façon dont le frai se produit). Le fil a été invoqué - jar, jnlp, explcled .classes dir, some container, etc.).

D'un autre côté, la liaison avec les méthodes statiques #main a aussi ses pièges. les modificateurs statiques ont une mauvaise tendance à s'infiltrer dans d'autres codes et sont généralement mal vus par les gens soucieux de la conception.

1
jasonnerothin