Comment puis-je redémarrer une application Java?


Answers

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        StringBuilder cmd = new StringBuilder();
        cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
        for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
            cmd.append(jvmArg + " ");
        }
        cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
        cmd.append(Main.class.getName()).append(" ");
        for (String arg : args) {
            cmd.append(arg).append(" ");
        }
        Runtime.getRuntime().exec(cmd.toString());
        System.exit(0);
    }
}

Dédié à tous ceux qui disent que c'est impossible.

Ce programme recueille toutes les informations disponibles pour reconstruire la ligne de commande d'origine. Puis, il le lance et puisqu'il s'agit de la même commande, votre application démarre une seconde fois. Ensuite, nous quittons le programme d'origine, le programme enfant reste en cours d'exécution (même sous Linux) et fait exactement la même chose.

AVERTISSEMENT : Si vous exécutez ceci, soyez conscient qu'il ne finit jamais de créer de nouveaux processus, similaires à une bombe de fourche .

Question

Comment puis-je redémarrer une application Java AWT? J'ai un bouton auquel j'ai attaché un gestionnaire d'événement. Quel code dois-je utiliser pour redémarrer l'application?

Je veux faire la même chose que Application.Restart() dans une application C #.




Similaire à la réponse « improved » de Yoda, mais avec d'autres améliorations (à la fois fonctionnelle, lisibilité et testabilité). Il est maintenant possible de s'exécuter en toute sécurité et redémarre autant de fois que le nombre d'arguments du programme donné.

  • Aucune accumulation d'options JAVA_TOOL_OPTIONS .
  • Recherche automatiquement la classe principale.
  • Hérite de stdout / stderr en cours.
public static void main(String[] args) throws Exception {
    if (args.length == 0)
        return;
    else
        args = Arrays.copyOf(args, args.length - 1);

    List<String> command = new ArrayList<>(32);
    appendJavaExecutable(command);
    appendVMArgs(command);
    appendClassPath(command);
    appendEntryPoint(command);
    appendArgs(command, args);

    System.out.println(command);
    try {
        new ProcessBuilder(command).inheritIO().start();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

private static void appendJavaExecutable(List<String> cmd) {
    cmd.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
}

private static void appendVMArgs(Collection<String> cmd) {
    Collection<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();

    String javaToolOptions = System.getenv("JAVA_TOOL_OPTIONS");
    if (javaToolOptions != null) {
        Collection<String> javaToolOptionsList = Arrays.asList(javaToolOptions.split(" "));
        vmArguments = new ArrayList<>(vmArguments);
        vmArguments.removeAll(javaToolOptionsList);
    }

    cmd.addAll(vmArguments);
}

private static void appendClassPath(List<String> cmd) {
    cmd.add("-cp");
    cmd.add(ManagementFactory.getRuntimeMXBean().getClassPath());
}

    private static void appendEntryPoint(List<String> cmd) {
    StackTraceElement[] stackTrace          = new Throwable().getStackTrace();
    StackTraceElement   stackTraceElement   = stackTrace[stackTrace.length - 1];
    String              fullyQualifiedClass = stackTraceElement.getClassName();
    String              entryMethod         = stackTraceElement.getMethodName();
    if (!entryMethod.equals("main"))
        throw new AssertionError("Entry point is not a 'main()': " + fullyQualifiedClass + '.' + entryMethod);

    cmd.add(fullyQualifiedClass);
}

private static void appendArgs(List<String> cmd, String[] args) {
    cmd.addAll(Arrays.asList(args));
}

V1.1 Bugfix: null pointeur si JAVA_TOOL_OPTIONS n'est pas défini

Exemple:

$ java -cp Temp.jar Temp a b c d e
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c, d]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp]
$



Strictement parlant, un programme Java ne peut pas se relancer puisqu'il doit tuer la JVM dans laquelle il s'exécute et le redémarrer, mais une fois que la JVM n'est plus exécutée (kill), aucune action ne peut être effectuée.

Vous pouvez faire quelques astuces avec les chargeurs de classes personnalisés pour charger, emballer et redémarrer les composants AWT, mais cela causera probablement beaucoup de maux de tête en ce qui concerne la boucle d'événements GUI.

Selon le lancement de l'application, vous pouvez démarrer la JVM dans un script wrapper contenant une boucle do / while, qui se poursuit pendant que la machine virtuelle Java quitte avec un code particulier, puis l'application AWT doit appeler System.exit(RESTART_CODE) . Par exemple, dans le pseudocode de script:

DO
  # Launch the awt program
  EXIT_CODE = # Get the exit code of the last process
WHILE (EXIT_CODE == RESTART_CODE)

L'application AWT doit quitter la JVM avec autre chose que RESTART_CODE sur une terminaison "normale" qui ne nécessite pas de redémarrage.




Je faisais des recherches sur le sujet moi-même quand est tombé sur cette question.

Indépendamment du fait que la réponse est déjà acceptée, je voudrais toujours offrir une approche alternative pour l'exhaustivité. Plus précisément, Apache Ant a servi de solution très flexible.

Fondamentalement, tout se résume à un fichier de script Ant avec une seule tâche d'exécution Java (voir here et here ) invoquée à partir d'un code Java (voir here ). Ce code Java, qui peut être un lancement de méthode, pourrait faire partie de l'application qui doit être redémarrée. L'application doit avoir une dépendance sur la bibliothèque Apache Ant (jar).

Chaque fois que l'application doit être redémarrée, elle doit appeler la méthode lancer et quitter la machine virtuelle. La tâche Ant Java doit avoir les options fork et spawn définies sur true.

Voici un exemple de script Ant:

<project name="applaucher" default="launch" basedir=".">
<target name="launch">
    <java classname="package.MasinClass" fork="true" spawn="true">
        <jvmarg value="-splash:splash.jpg"/>
        <jvmarg value="-D other VM params"/>
        <classpath>
            <pathelement location="lib-1.jar" />
            ...
            <pathelement location="lib-n.jar" />
        </classpath>
    </java>
</target>
</project>

Le code de la méthode de lancement peut ressembler à ceci:

public final void launch(final String antScriptFile) {
 /* configure Ant and execute the task */
   final File buildFile = new File(antScriptFile);
   final Project p = new Project();
   p.setUserProperty("ant.file", buildFile.getAbsolutePath());

   final DefaultLogger consoleLogger = new DefaultLogger();
   consoleLogger.setErrorPrintStream(System.err);
   consoleLogger.setOutputPrintStream(System.out);
   consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
   p.addBuildListener(consoleLogger);

   try {
       p.fireBuildStarted();
       p.init();
       final ProjectHelper helper = ProjectHelper.getProjectHelper();
       p.addReference("ant.projectHelper", helper);
       helper.parse(p, buildFile);
       p.executeTarget(p.getDefaultTarget());
       p.fireBuildFinished(null);
   } catch (final BuildException e) {
       p.fireBuildFinished(e);
   }

   /* exit the current VM */
   System.exit(0);

}

Une chose très pratique ici est que le même script est utilisé pour le démarrage initial de l'application ainsi que pour les redémarrages.




Il suffit d'ajouter des informations qui ne sont pas présentes dans les autres réponses.

Si procfs /proc/self/cmdline est disponible

Si vous utilisez un environnement qui fournit procfs et que le système de fichiers /proc est disponible (ce qui signifie que ce n'est pas une solution portable), vous pouvez avoir Java /proc/self/cmdline pour redémarrer, comme ceci:

public static void restart() throws IOException {
    new ProcessBuilder(getMyOwnCmdLine()).inheritIO().start();
}
public static String[] getMyOwnCmdLine() throws IOException {
    return readFirstLine("/proc/self/cmdline").split("\u0000");
}
public static String readFirstLine(final String filename) throws IOException {
    try (final BufferedReader in = new BufferedReader(new FileReader(filename))) {
        return in.readLine();
    }
}

Sur les systèmes avec /proc/self/cmdline disponibles, c'est probablement la façon la plus élégante de "redémarrer" le processus Java actuel à partir de Java. Aucun JNI impliqué, et aucune estimation des chemins et des choses requises.

De nombreux systèmes UNIX, y compris GNU / Linux (y compris Android) ont maintenant procfs Cependant, sur certains comme FreeBSD, il est obsolète et en cours de suppression. Mac OS X est une exception en ce sens qu'il n'a pas de procfs . Windows n'a pas non plus procfs . Cygwin a procfs mais il est invisible pour Java car il n'est visible que pour les applications utilisant les DLL Cygwin au lieu des appels système Windows, et Java n'est pas au courant de Cygwin.

Ne pas oublier d'utiliser ProcessBuilder.inheritIO()

La valeur par défaut est que stdin / stdout / stderr (en Java, appelé System.in / System.out / System.err ) du processus démarré sont définis sur des canaux qui permettent au processus en cours de communiquer avec le processus nouvellement démarré. Si vous voulez redémarrer le processus en cours, ce n'est probablement pas ce que vous voulez . Au lieu de cela, vous voudriez que stdin / stdout / stderr soient les mêmes que ceux de la VM actuelle. Ceci est appelé hérité . Vous pouvez le faire en appelant inheritIO() de votre instance ProcessBuilder .

Piège sur Windows

Un cas d'utilisation fréquent d'une fonction restart() consiste à redémarrer l'application après une mise à jour. La dernière fois que j'ai essayé cela sur Windows c'était problématique. Lors de l'écrasement du fichier .jar l'application avec la nouvelle version, l'application a commencé à se comporter de manière incorrecte et à donner des exceptions au fichier .jar . Je dis juste, au cas où c'est votre cas d'utilisation. À l'époque, j'ai résolu le problème en enveloppant l'application dans un fichier batch et en utilisant une valeur de retour magique de System.exit() que j'ai interrogée dans le fichier de commandes et que le fichier batch redémarre l'application à la place.




Si vous avez vraiment besoin de redémarrer votre application, vous pouvez écrire une application séparée le démarrer ...

Cette page fournit de nombreux exemples différents pour différents scénarios:

http://www.rgagnon.com/javadetails/java-0014.html