web-dev-qa-db-fra.com

Redémarrez par programme l'application Spring Boot/Actualisez le contexte Spring

J'essaie de redémarrer par programme mon application Spring sans que l'utilisateur ne soit obligé d'intervenir.

En gros, j’ai une page qui permet de changer le mode de l’application (c’est-à-dire de changer de profil actif) et, autant que je sache, je dois redémarrer le contexte.

Actuellement, mon code est très simple, il ne concerne que le bit de redémarrage (c'est Kotlin d'ailleurs):

    context.close()
    application.setEnvironment(context.environment)
    ClassUtils.overrideThreadContextClassLoader(application.javaClass.classLoader)
    context = application.run(*argsArray)

Cependant, au moment où je fais context.close() la JVM existe immédiatement. J'ai également essayé context.refresh() mais cela semble tuer simplement Tomcat/Jetty (essayé les deux au cas où il s'agirait d'un problème de Tomcat) et rien ne se passe.

J'ai également vu redémarrer par programme l'application Spring Boot mais rien ne semble fonctionner pour moi à partir de ces réponses. De plus, j'ai examiné Spring Actuator, qui est supposé avoir le point final /restart, mais cela ne semble plus être là?

De l'aide serait grandement appréciée. Merci.

9
Crembo

Même si la solution d'Alex fonctionne, je ne crois pas à l'inclusion de 2 dépendances supplémentaires (Actuator et Cloud Context) juste pour pouvoir effectuer une opération. Au lieu de cela, j'ai combiné sa réponse et modifié mon code afin de faire ce que je voulais.

Donc, tout d’abord, il est crucial que le code soit exécuté en utilisant new Thread() et setDaemon(false);. J'ai la méthode d'extrémité suivante qui gère le redémarrage:

val restartThread = Thread {
    logger.info("Restarting...")
    Thread.sleep(1000)
    SpringMain.restartToMode(AppMode.valueOf(change.newMode.toUpperCase()))
    logger.info("Restarting... Done.")
}
restartThread.isDaemon = false
restartThread.start()

La Thread.sleep(1000) n'est pas obligatoire, mais je souhaite que mon contrôleur affiche la vue avant de redémarrer réellement l'application.

SpringMain.restartToMode a les caractéristiques suivantes:

@Synchronized fun restartToMode(mode: AppMode) {
    requireNotNull(context)
    requireNotNull(application)

    // internal logic to potentially produce a new arguments array

    // close previous context
    context.close()

    // and build new one using the new mode
    val builder = SpringApplicationBuilder(SpringMain::class.Java)
    application = builder.application()
    context = builder.build().run(*argsArray)
}

context et application proviennent de la méthode main au démarrage de l'application:

val args = ArrayList<String>()
lateinit var context: ConfigurableApplicationContext
lateinit var application: SpringApplication

@Throws(Exception::class)
@JvmStatic fun main(args: Array<String>) {
    this.args += args

    val builder = SpringApplicationBuilder(SpringMain::class.Java)
    application = builder.application()
    context = builder.build().run(*args)
}

Je ne suis pas tout à fait sûr si cela pose des problèmes. S'il y en aura, je mettrai à jour cette réponse. Espérons que cela aidera les autres.

10
Crembo

Au cas où cela pourrait aider quelqu'un, voici une traduction en Java pura de la réponse acceptée de Crembo.

Méthode du contrôleur:

@GetMapping("/restart")
void restart() {
    Thread restartThread = new Thread(() -> {
        try {
            Thread.sleep(1000);
            Main.restart();
        } catch (InterruptedException ignored) {
        }
    });
    restartThread.setDaemon(false);
    restartThread.start();
}

Classe principale (bits significatifs uniquement):

private static String[] args;
private static ConfigurableApplicationContext context;

public static void main(String[] args) {
    Main.args = args;
    Main.context = SpringApplication.run(Main.class, args);
}

public static void restart() {
    // close previous context
    context.close();

    // and build new one
    Main.context = SpringApplication.run(Main.class, args);

}
6
Olivier Gérardin

Vous pouvez utiliser la variable RestartEndPoint (dans spring-cloud-context) pour redémarrer l'application Spring Boot par programme:

@Autowired
private RestartEndpoint restartEndpoint;

...

Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();

Cela fonctionne, même s'il lève une exception pour vous informer que cela peut entraîner des fuites de mémoire:

L'application Web [xyx] semble avoir démarré un thread nommé [Fil-6] mais n'a pas réussi à l'arrêter. Ceci est très susceptible de créer un fuite de mémoire. Trace de fil de pile:

La même réponse a été fournie pour cette autre question (libellée différemment): Appelez l'actionneur Spring/le point de terminaison restart depuis Spring Boot à l'aide d'une fonction Java

3
alexbt

J'ai résolu ce problème en utilisant Restarter à partir de Spring Devtools . Ajoutez ceci à pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>

Ensuite, utilisez org.springframework.boot.devtools.restart.Restarter pour appeler ceci:

Restarter.getInstance().restart();

Ça marche pour moi. J'espère que cette aide.

0
Lucifer Nick