Introduce ConfigurableApplicationContext.restart() method
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details

Closes gh-35171
This commit is contained in:
Juergen Hoeller 2025-07-08 18:50:05 +02:00
parent b48da4c0c2
commit 523552ac2e
5 changed files with 99 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -348,6 +348,40 @@ class DefaultLifecycleProcessorTests {
context.close();
}
@Test
void contextRefreshThenRestartWithMixedBeans() {
StaticApplicationContext context = new StaticApplicationContext();
CopyOnWriteArrayList<Lifecycle> 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();
}
}
}