以编程方式重新启动Spring Boot应用程序/刷新Spring Context

Cre*_*mbo 9 java spring spring-mvc kotlin spring-boot

我试图以编程方式重新启动我的Spring应用程序,而无需用户进行干预.

基本上,我有一个页面,允许切换应用程序的模式(实际上意味着切换当前活动的配置文件),据我所知,我必须重新启动上下文.

目前我的代码非常简单,它仅用于重启位(顺便说一下,这是Kotlin):

    context.close()
    application.setEnvironment(context.environment)
    ClassUtils.overrideThreadContextClassLoader(application.javaClass.classLoader)
    context = application.run(*argsArray)
Run Code Online (Sandbox Code Playgroud)

但是,我现在context.close()就立即存在JVM.我也试过了,context.refresh()但这似乎只是简单地杀死了Tomcat/Jetty(只是为了防止这是一个Tomcat问题)然后没有任何反应.

我也看过以编程方式重新启动Spring Boot应用程序,但似乎没有任何东西可以帮助我解决这些问题.此外,我查看了Spring Actuator,它应该具有/restart端点,但似乎不再存在了?

非常感谢帮助.谢谢.

Cre*_*mbo 10

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

因此,首先,使用和执行代码至关重要.我有以下端点方法来处理重启: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()
Run Code Online (Sandbox Code Playgroud)

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

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)
}
Run Code Online (Sandbox Code Playgroud)

在开始申请时的方法context和application来源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)
}
Run Code Online (Sandbox Code Playgroud)

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


Oli*_*din 7

如果它可以帮助某人,这里是Crembo接受的答案的pura Java翻译.

控制器方法:

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

主类(仅限有效位):

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

}
Run Code Online (Sandbox Code Playgroud)

  • 好吧,显然这种方法只能工作一次......第二次我在 context.close() 上得到一个 NPE。既然它是 Main 的静态成员,它怎么可能为 null ?我在这里很困惑。 (2认同)
  • 引用已接受的答案:“不需要 Thread.sleep(1000),但我希望我的控制器在实际重新启动应用程序之前输出视图。” (2认同)

小智 7

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

可以通过让重新启动线程使用与初始主调用线程相同的类加载器来避免此 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();
}
Run Code Online (Sandbox Code Playgroud)


Luc*_*ick 6

我通过使用 Spring Devtools 的 Restarter 解决了这个问题。将其添加到 pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>
Run Code Online (Sandbox Code Playgroud)

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

Restarter.getInstance().restart();
Run Code Online (Sandbox Code Playgroud)

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