Programmatically restart Spring Boot application / Refresh Spring Context

后端 未结 6 1594
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-03 15:43

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 a

相关标签:
6条回答
  • 2020-12-03 15:51

    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

    0 讨论(0)
  • 2020-12-03 15:58

    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.

    0 讨论(0)
  • 2020-12-03 16:00

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

    }`

    0 讨论(0)
  • 2020-12-03 16:08

    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.

    0 讨论(0)
  • 2020-12-03 16:09

    As was commented already, the restart-via-thread implementations given before only work once, and second time around throw a NPE because context is null.

    This NPE can be avoided by having the restart thread use the same class loader as the initial main-invoking thread:

    private static volatile ConfigurableApplicationContext context;
    private static ClassLoader mainThreadClassLoader;
    
    public static void main(String[] args) {
        mainThreadClassLoader = Thread.currentThread().getContextClassLoader();
        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.setContextClassLoader(mainThreadClassLoader);
        thread.setDaemon(false);
        thread.start();
    }
    
    0 讨论(0)
  • 2020-12-03 16:10

    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);
    
    }
    
    0 讨论(0)
提交回复
热议问题