From 523552ac2e26c9a4df16b5e66ec8339da7c1c810 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 8 Jul 2025 18:50:05 +0200 Subject: [PATCH] Introduce ConfigurableApplicationContext.restart() method Closes gh-35171 --- .../ConfigurableApplicationContext.java | 11 ++++ .../context/LifecycleProcessor.java | 26 ++++++-- .../support/AbstractApplicationContext.java | 6 ++ .../support/DefaultLifecycleProcessor.java | 10 +++ .../DefaultLifecycleProcessorTests.java | 61 +++++++++++++++---- 5 files changed, 99 insertions(+), 15 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java index f383fbc7175..6f56d8a6e51 100644 --- a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -220,6 +220,17 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life */ void refresh() throws BeansException, IllegalStateException; + /** + * Stop all beans in this application context if necessary, and subsequently + * restart all auto-startup beans, effectively restoring the lifecycle state + * after {@link #refresh()} (typically after a preceding {@link #stop()} call + * when a full {@link #start()} of even lazy-starting beans is to be avoided). + * @since 7.0 + * @see #stop() + * @see SmartLifecycle#isAutoStartup() + */ + void restart(); + /** * Register a shutdown hook with the JVM runtime, closing this context * on JVM shutdown unless it has already been closed at that time. diff --git a/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java index ae01c5dab3b..d9d4ea446fb 100644 --- a/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java @@ -26,13 +26,31 @@ package org.springframework.context; public interface LifecycleProcessor extends Lifecycle { /** - * Notification of context refresh, for example, for auto-starting components. + * Notification of context refresh for auto-starting components. + * @see ConfigurableApplicationContext#refresh() */ - void onRefresh(); + default void onRefresh() { + start(); + } /** - * Notification of context close phase, for example, for auto-stopping components. + * Notification of context restart for auto-stopping and subsequently + * auto-starting components. + * @since 7.0 + * @see ConfigurableApplicationContext#restart() */ - void onClose(); + default void onRestart() { + stop(); + start(); + } + + /** + * Notification of context close phase for auto-stopping components + * before destruction. + * @see ConfigurableApplicationContext#close() + */ + default void onClose() { + stop(); + } } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 1cdd4598d7e..2daed0cee3e 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1548,6 +1548,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader publishEvent(new ContextStoppedEvent(this)); } + @Override + public void restart() { + getLifecycleProcessor().onRestart(); + publishEvent(new ContextStartedEvent(this)); + } + @Override public boolean isRunning() { return (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning()); diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java index bb29932ccba..f5760107f59 100644 --- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java @@ -314,6 +314,16 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor this.running = true; } + @Override + public void onRestart() { + this.stoppedBeans = null; + if (this.running) { + stopBeans(); + } + startBeans(true); + this.running = true; + } + @Override public void onClose() { stopBeans(); diff --git a/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java b/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java index ce8b33830ee..cb968cf9c12 100644 --- a/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java @@ -348,6 +348,40 @@ class DefaultLifecycleProcessorTests { context.close(); } + @Test + void contextRefreshThenRestartWithMixedBeans() { + StaticApplicationContext context = new StaticApplicationContext(); + CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList<>(); + TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forShutdownTests(5, 0, stoppedBeans); + TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forShutdownTests(-3, 0, stoppedBeans); + smartBean2.setAutoStartup(false); + context.getBeanFactory().registerSingleton("smartBean1", smartBean1); + context.getBeanFactory().registerSingleton("smartBean2", smartBean2); + + assertThat(smartBean1.isRunning()).isFalse(); + assertThat(smartBean2.isRunning()).isFalse(); + context.refresh(); + assertThat(smartBean1.isRunning()).isTrue(); + assertThat(smartBean2.isRunning()).isFalse(); + context.restart(); + assertThat(stoppedBeans).containsExactly(smartBean1); + assertThat(smartBean1.isRunning()).isTrue(); + assertThat(smartBean2.isRunning()).isFalse(); + smartBean1.stop(); + assertThat(stoppedBeans).containsExactly(smartBean1, smartBean1); + assertThat(smartBean1.isRunning()).isFalse(); + assertThat(smartBean2.isRunning()).isFalse(); + context.restart(); + assertThat(stoppedBeans).containsExactly(smartBean1, smartBean1); + assertThat(smartBean1.isRunning()).isTrue(); + assertThat(smartBean2.isRunning()).isFalse(); + context.start(); + assertThat(smartBean1.isRunning()).isTrue(); + assertThat(smartBean2.isRunning()).isTrue(); + context.close(); + assertThat(stoppedBeans).containsExactly(smartBean1, smartBean1, smartBean1, smartBean2); + } + @Test @EnabledForTestGroups(LONG_RUNNING) void smartLifecycleGroupShutdown() { @@ -741,17 +775,22 @@ class DefaultLifecycleProcessorTests { // invocation order in the 'stoppedBeans' list stop(); final int delay = this.shutdownDelay; - new Thread(() -> { - try { - Thread.sleep(delay); - } - catch (InterruptedException e) { - // ignore - } - finally { - callback.run(); - } - }).start(); + if (delay > 0) { + new Thread(() -> { + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + // ignore + } + finally { + callback.run(); + } + }).start(); + } + else { + callback.run(); + } } }