Programmatically restart Spring Boot application /

2020-02-01 04:42发布

问题:

I am trying to programmatically restart my Spring Application without having the user to intervene.

Basically, I have a page which allows to switch the mode of the application (actually meaning switching the currently active profile) and as far as I understand I must restart the context.

Currently my code is very simple, it's just for the restarting bit (this is Kotlin by the way):

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

However the moment I do context.close() the JVM exists immediately. I have also tried context.refresh() but that seems to simply kill Tomcat/Jetty (tried both just in case it was a Tomcat problem) and then nothing happens.

I have also seen Programmatically restart Spring Boot application but nothing seems to work for me from those answers. Furthermore, I looked into Spring Actuator which supposedly has the /restart endpoint, but that doesn't seem to be there anymore?

回答1:

Even though Alex's solution works, I don't believe in including 2 additional dependencies (Actuator and Cloud Context) just to be able to do one operation. Instead, I have combined his answer and modified my code in order to do what I wanted.

So, first of all, it is crucial that the code is executed using new Thread() and setDaemon(false);. I have the following endpoint method that handles the restart:

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

The Thread.sleep(1000) is not required, but I want my controller to output the view before actually restarting the application.

SpringMain.restartToMode has the following:

@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)
}

Where context and application come from the main method upon starting the 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)
}

I am not entirely sure if this produces any problems. If there will be, I will update this answer. Hopefully this will be of any help to others.



回答2:

In case it might help someone, here's a pura Java translation of Crembo's accepted answer.

Controller method:

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

Main class (significant bits only):

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

}


回答3:

You can use the RestartEndPoint (in spring-cloud-context dependency) to restart the Spring Boot application programmatically:

@Autowired
private RestartEndpoint restartEndpoint;

...

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

It works, even though it will throw an exception to inform you that this may lead to memory leaks:

The web application [xyx] appears to have started a thread named [Thread-6] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

The same answer was provided for this other question (worded differently): Call Spring actuator /restart endpoint from Spring boot using a java function



回答4:

I've solved this issue by using Restarter from Spring Devtools. Add this to pom.xml:

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

Then use org.springframework.boot.devtools.restart.Restarter to call this:

Restarter.getInstance().restart();

It works for me. Hope this help.



回答5:

Below restart method will work.

`@SpringBootApplication public class Application {

private static ConfigurableApplicationContext context;

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

public static void restart() {
    ApplicationArguments args = context.getBean(ApplicationArguments.class);

    Thread thread = new Thread(() -> {
        context.close();
        context = SpringApplication.run(Application.class, args.getSourceArgs());
    });

    thread.setDaemon(false);
    thread.start();
}

}`