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



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

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

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

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

然而,当我执行context.close()时,JVM立即存在。我也试过context.refresh(),但这似乎只是杀死Tomcat/Jetty(尝试了两个以防万一是Tomcat的问题),然后什么也没发生。

我也见过以编程方式重新启动Spring启动应用程序,但从这些答案中似乎没有什么对我有用。此外,我调查了弹簧致动器,据说有/restart端点,但似乎不再存在了?

即使Alex的解决方案工作,我不相信包括2个额外的依赖关系(ActuatorCloud 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()

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

启动应用程序时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)
}

我不完全确定这是否会产生任何问题。如果有,我会更新这个答案。

如果它可能对某人有所帮助,这里有一个纯Java翻译的Crembo的公认答案。

控制器方法:

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

主类(仅有效位):

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

如前所述,前面给出的通过线程重新启动的实现只工作一次,第二次会抛出NPE,因为context为空。

可以通过让重启线程使用与初始主调用线程相同的类装入器来避免这种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();
}

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

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

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

Restarter.getInstance().restart();

这对我有用。

您可以使用RestartEndPoint(在spring-cloud-context依赖项中)以编程方式重新启动Spring Boot应用程序:

@Autowired
private RestartEndpoint restartEndpoint;
...
Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();

它可以工作,即使它会抛出一个异常,通知您这可能导致内存泄漏:

web应用程序[xyx]似乎已经启动了一个名为[Thread-6]但未能停止它。这很可能会产生内存泄漏。线程的堆栈跟踪:

对于另一个问题(措辞不同)提供了相同的答案:使用java函数

从Spring启动调用Spring执行器/重启端点

下面的重启方法可以工作。

@SpringBootApplication公共类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();
}

} '

最新更新