java 以编程方式重新启动 Spring Boot 应用程序/刷新 Spring 上下文

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39386168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 04:19:57  来源:igfitidea点击:

Programmatically restart Spring Boot application / Refresh Spring Context

javaspringspring-mvcspring-bootkotlin

提问by Crembo

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

我正在尝试以编程方式重新启动我的 Spring 应用程序,而无需用户干预。

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

目前我的代码非常简单,只是为了重启(顺便说一下,这是 Kotlin):

    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.

然而,在我做context.close()JVM的那一刻,它立即存在。我也尝试过,context.refresh()但这似乎只是杀死了 Tomcat/Jetty(都尝试过,以防万一是 Tomcat 问题),然后什么也没有发生。

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

我还看到了以编程方式重新启动 Spring Boot 应用程序,但从这些答案中似乎没有任何对我有用。此外,我查看了据说具有/restart端点的Spring Actuator ,但它似乎不再存在了?

回答by Crembo

Even though Alex's solution works, I don't believe in including 2 additional dependencies (Actuatorand 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.

即使 Alex 的解决方案有效,我也不相信为了能够执行一项操作Actuator而包含 2 个额外的依赖项(和Cloud Context)。相反,我结合了他的回答并修改了我的代码,以便做我想做的事。

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

因此,首先,使用and执行代码是至关重要的。我有以下处理重启的端点方法:new Thread()setDaemon(false);

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.

Thread.sleep(1000)不是必需的,但我希望我的控制器输出实际上重新启动应用程序前视图。

SpringMain.restartToModehas the following:

SpringMain.restartToMode有以下内容:

@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 contextand applicationcome from the mainmethod upon starting the application:

contextapplication来自main在启动应用程序的方法:

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.

我不完全确定这是否会产生任何问题。如果有的话,我会更新这个答案。希望这对其他人有任何帮助。

回答by Olivier Gérardin

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

如果它可能对某人有所帮助,这里是 Crembo 接受的答案的 pura Java 翻译。

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

}

回答by alexbt

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

您可以使用RestartEndPointspring-cloud-context依赖)以编程方式重新启动 Spring Boot 应用程序:

@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:

Web 应用程序 [xyx] 似乎启动了一个名为 [Thread-6] 的线程,但未能将其停止。这很可能造成内存泄漏。线程的堆栈跟踪:

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

为另一个问题提供了相同的答案(措辞不同)使用 java 函数从 Spring boot 调用 Spring 执行器 /restart 端点

回答by johnwoo

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.

正如已经评论过的那样,之前给出的重启通过线程实现只能工作一次,第二次因为上下文为空而抛出 NPE。

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

可以通过让重启线程使用与初始主调用线程相同的类加载器来避免这种 NPE:

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

回答by Lucifer Nick

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

我已经通过使用 Spring Devtools 中的 Restarter 解决了这个问题。将此添加到 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:

然后使用 org.springframework.boot.devtools.restart.Restarter 来调用这个:

Restarter.getInstance().restart();

It works for me. Hope this help.

这个对我有用。希望这有帮助。

回答by Chetan Laddha

Below restart method will work.

下面的重启方法将起作用。

`@SpringBootApplication public class Application {

`@SpringBootApplication 公共类应用程序{

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

}`

}`