diff --git a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java index 87adeaed3e5..7ab4a8bd948 100644 --- a/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java +++ b/spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java @@ -391,7 +391,6 @@ class QuartzSupportTests { try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) { JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class)); assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse(); - ctx.stop(); ctx.restart(); } } 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 6f56d8a6e51..8dffb08bd69 100644 --- a/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java @@ -221,16 +221,27 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life void refresh() throws BeansException, IllegalStateException; /** - * Stop all beans in this application context if necessary, and subsequently + * Pause 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 #pause() + * @see #start() * @see SmartLifecycle#isAutoStartup() */ void restart(); + /** + * Stop all beans in this application context unless they explicitly opt out of + * pausing through {@link SmartLifecycle#isPauseable()} returning {@code false}. + * @since 7.0 + * @see #restart() + * @see #stop() + * @see SmartLifecycle#isPauseable() + */ + void pause(); + /** * 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 d9d4ea446fb..c2e39a84d3d 100644 --- a/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java @@ -44,6 +44,15 @@ public interface LifecycleProcessor extends Lifecycle { start(); } + /** + * Notification of context pause for auto-stopping components. + * @since 7.0 + * @see ConfigurableApplicationContext#pause() + */ + default void onPause() { + stop(); + } + /** * Notification of context close phase for auto-stopping components * before destruction. diff --git a/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java b/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java index ac2039cae68..cf4c43bd264 100644 --- a/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java +++ b/spring-context/src/main/java/org/springframework/context/SmartLifecycle.java @@ -85,7 +85,7 @@ public interface SmartLifecycle extends Lifecycle, Phased { /** * Returns {@code true} if this {@code Lifecycle} component should get * started automatically by the container at the time that the containing - * {@link ApplicationContext} gets refreshed. + * {@link ApplicationContext} gets refreshed or restarted. *
A value of {@code false} indicates that the component is intended to * be started through an explicit {@link #start()} call instead, analogous * to a plain {@link Lifecycle} implementation. @@ -93,12 +93,35 @@ public interface SmartLifecycle extends Lifecycle, Phased { * @see #start() * @see #getPhase() * @see LifecycleProcessor#onRefresh() + * @see LifecycleProcessor#onRestart() * @see ConfigurableApplicationContext#refresh() + * @see ConfigurableApplicationContext#restart() */ default boolean isAutoStartup() { return true; } + /** + * Returns {@code true} if this {@code Lifecycle} component is able to + * participate in a restart sequence, receiving corresponding {@link #stop()} + * and {@link #start()} calls with a potential pause in-between. + *
A value of {@code false} indicates that the component prefers to + * be skipped in a pause scenario, neither receiving a {@link #stop()} + * call nor a subsequent {@link #start()} call, analogous to a plain + * {@link Lifecycle} implementation. It will only receive a {@link #stop()} + * call on close and on explicit context-wide stopping but not on pause. + *
The default implementation returns {@code true}. + * @since 7.0 + * @see #stop() + * @see LifecycleProcessor#onPause() + * @see LifecycleProcessor#onClose() + * @see ConfigurableApplicationContext#pause() + * @see ConfigurableApplicationContext#close() + */ + default boolean isPauseable() { + return true; + } + /** * Indicates that a Lifecycle component must stop if it is currently running. *
The provided callback is used by the {@link LifecycleProcessor} to support diff --git a/spring-context/src/main/java/org/springframework/context/event/ContextPausedEvent.java b/spring-context/src/main/java/org/springframework/context/event/ContextPausedEvent.java new file mode 100644 index 00000000000..759783bc0b2 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/event/ContextPausedEvent.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Event raised when an {@code ApplicationContext} gets paused. + * + *
Note that {@code ContextPausedEvent} is a specialization of
+ * {@link ContextStoppedEvent}.
+ *
+ * @author Juergen Hoeller
+ * @since 7.0
+ * @see ConfigurableApplicationContext#pause()
+ * @see ContextRestartedEvent
+ * @see ContextStoppedEvent
+ */
+@SuppressWarnings("serial")
+public class ContextPausedEvent extends ContextStoppedEvent {
+
+ /**
+ * Create a new {@code ContextRestartedEvent}.
+ * @param source the {@code ContextPausedEvent} that has been restarted
+ * (must not be {@code null})
+ */
+ public ContextPausedEvent(ApplicationContext source) {
+ super(source);
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/event/ContextRestartedEvent.java b/spring-context/src/main/java/org/springframework/context/event/ContextRestartedEvent.java
index e8165be8d08..8ac44108917 100644
--- a/spring-context/src/main/java/org/springframework/context/event/ContextRestartedEvent.java
+++ b/spring-context/src/main/java/org/springframework/context/event/ContextRestartedEvent.java
@@ -17,6 +17,7 @@
package org.springframework.context.event;
import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
/**
* Event raised when an {@code ApplicationContext} gets restarted.
@@ -26,8 +27,9 @@ import org.springframework.context.ApplicationContext;
*
* @author Sam Brannen
* @since 7.0
+ * @see ConfigurableApplicationContext#restart()
+ * @see ContextPausedEvent
* @see ContextStartedEvent
- * @see ContextStoppedEvent
*/
@SuppressWarnings("serial")
public class ContextRestartedEvent extends ContextStartedEvent {
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 cc846e3e5f3..d741a1f25c2 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
@@ -66,6 +66,7 @@ import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextPausedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextRestartedEvent;
import org.springframework.context.event.ContextStartedEvent;
@@ -1555,6 +1556,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
publishEvent(new ContextRestartedEvent(this));
}
+ @Override
+ public void pause() {
+ getLifecycleProcessor().onPause();
+ publishEvent(new ContextPausedEvent(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 f5760107f59..02bf8c08ab5 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
@@ -287,7 +287,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
*/
@Override
public void stop() {
- stopBeans();
+ stopBeans(false);
this.running = false;
}
@@ -308,7 +308,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
catch (ApplicationContextException ex) {
// Some bean failed to auto-start within context refresh:
// stop already started beans on context refresh failure.
- stopBeans();
+ stopBeans(false);
throw ex;
}
this.running = true;
@@ -318,15 +318,23 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
public void onRestart() {
this.stoppedBeans = null;
if (this.running) {
- stopBeans();
+ stopBeans(true);
}
startBeans(true);
this.running = true;
}
+ @Override
+ public void onPause() {
+ if (this.running) {
+ stopBeans(true);
+ this.running = false;
+ }
+ }
+
@Override
public void onClose() {
- stopBeans();
+ stopBeans(false);
this.running = false;
}
@@ -341,7 +349,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
void stopForRestart() {
if (this.running) {
this.stoppedBeans = ConcurrentHashMap.newKeySet();
- stopBeans();
+ stopBeans(false);
this.running = false;
}
}
@@ -361,7 +369,8 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
int startupPhase = getPhase(bean);
- phases.computeIfAbsent(startupPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, autoStartupOnly))
+ phases.computeIfAbsent(
+ startupPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, autoStartupOnly, false))
.add(beanName, bean);
}
});
@@ -424,13 +433,14 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
(!(bean instanceof SmartLifecycle smartLifecycle) || smartLifecycle.isAutoStartup()));
}
- private void stopBeans() {
+ private void stopBeans(boolean pauseableOnly) {
Map If the cached application context was previously
- * {@linkplain org.springframework.context.Lifecycle#stop() stopped}, it
- * must be
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#pause() paused},
+ * it must be
* {@linkplain org.springframework.context.support.AbstractApplicationContext#restart()
* restarted}. This applies to parent contexts as well.
* In addition, the {@linkplain #getHitCount() hit} and
@@ -187,7 +187,7 @@ public interface ContextCache {
* {@link MergedContextConfiguration} and any of its parents.
* If no other test classes are actively using the same application
* context(s), the application context(s) should be
- * {@linkplain org.springframework.context.Lifecycle#stop() stopped}.
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#pause() paused}.
* The default implementation of this method does nothing. Concrete
* implementations are therefore highly encouraged to override this
* method, {@link #registerContextUsage(MergedContextConfiguration, Class)},
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
index ad52643bd59..5451fb5854a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
@@ -187,7 +187,7 @@ public class DefaultContextCache implements ContextCache {
activeTestClasses.remove(testClass);
if (activeTestClasses.isEmpty()) {
if (context instanceof ConfigurableApplicationContext cac && cac.isRunning()) {
- cac.stop();
+ cac.pause();
}
this.contextUsageMap.remove(mergedConfig);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/UnusedContextsIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/UnusedContextsIntegrationTests.java
index 9e861a1aee0..49dac4c34a1 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/UnusedContextsIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/UnusedContextsIntegrationTests.java
@@ -69,13 +69,13 @@ class UnusedContextsIntegrationTests {
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase1",
- "ContextStopped:TestCase1",
+ "ContextPaused:TestCase1",
// --- TestCase2 -----------------------------------------------
"ContextRestarted:TestCase1",
"BeforeTestClass:TestCase2",
"AfterTestClass:TestCase2",
- "ContextStopped:TestCase1",
+ "ContextPaused:TestCase1",
// --- TestCase3 -----------------------------------------------
"ContextRestarted:TestCase1",
@@ -90,13 +90,13 @@ class UnusedContextsIntegrationTests {
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase4",
- "ContextStopped:TestCase4",
+ "ContextPaused:TestCase4",
// --- TestCase5 -----------------------------------------------
"ContextRestarted:TestCase4",
"BeforeTestClass:TestCase5",
"AfterTestClass:TestCase5",
- "ContextStopped:TestCase4"
+ "ContextPaused:TestCase4"
);
}
@@ -130,19 +130,19 @@ class UnusedContextsIntegrationTests {
// using the context
"AfterTestClass:OverridingNestedTestCase1",
- "ContextStopped:OverridingNestedTestCase1",
+ "ContextPaused:OverridingNestedTestCase1",
// --- OverridingNestedTestCase2 ---------------------------
"ContextRestarted:OverridingNestedTestCase1",
"BeforeTestClass:OverridingNestedTestCase2",
"AfterTestClass:OverridingNestedTestCase2",
- "ContextStopped:OverridingNestedTestCase1",
+ "ContextPaused:OverridingNestedTestCase1",
"AfterTestClass:NestedTestCase",
// No Stopped event, since EnclosingTestCase is still using the context
"AfterTestClass:EnclosingTestCase",
- "ContextStopped:EnclosingTestCase"
+ "ContextPaused:EnclosingTestCase"
);
}
@@ -161,23 +161,23 @@ class UnusedContextsIntegrationTests {
// --- ContextHierarchyLevel1TestCase ------------------------------
"ContextRefreshed:ContextHierarchyLevel1TestCase",
"AfterTestClass:ContextHierarchyLevel1TestCase",
- "ContextStopped:ContextHierarchyLevel1TestCase",
+ "ContextPaused:ContextHierarchyLevel1TestCase",
// --- ContextHierarchyLevel2TestCase ------------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase",
"ContextRefreshed:ContextHierarchyLevel2TestCase",
"AfterTestClass:ContextHierarchyLevel2TestCase",
- "ContextStopped:ContextHierarchyLevel2TestCase",
- "ContextStopped:ContextHierarchyLevel1TestCase",
+ "ContextPaused:ContextHierarchyLevel2TestCase",
+ "ContextPaused:ContextHierarchyLevel1TestCase",
// --- ContextHierarchyLevel3a1TestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase",
"ContextRestarted:ContextHierarchyLevel2TestCase",
"ContextRefreshed:ContextHierarchyLevel3a1TestCase",
"AfterTestClass:ContextHierarchyLevel3a1TestCase",
- "ContextStopped:ContextHierarchyLevel3a1TestCase",
- "ContextStopped:ContextHierarchyLevel2TestCase",
- "ContextStopped:ContextHierarchyLevel1TestCase",
+ "ContextPaused:ContextHierarchyLevel3a1TestCase",
+ "ContextPaused:ContextHierarchyLevel2TestCase",
+ "ContextPaused:ContextHierarchyLevel1TestCase",
// --- ContextHierarchyLevel3a2TestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase",
@@ -185,18 +185,18 @@ class UnusedContextsIntegrationTests {
"ContextRestarted:ContextHierarchyLevel3a1TestCase",
"BeforeTestClass:ContextHierarchyLevel3a2TestCase",
"AfterTestClass:ContextHierarchyLevel3a2TestCase",
- "ContextStopped:ContextHierarchyLevel3a1TestCase",
- "ContextStopped:ContextHierarchyLevel2TestCase",
- "ContextStopped:ContextHierarchyLevel1TestCase",
+ "ContextPaused:ContextHierarchyLevel3a1TestCase",
+ "ContextPaused:ContextHierarchyLevel2TestCase",
+ "ContextPaused:ContextHierarchyLevel1TestCase",
// --- ContextHierarchyLevel3bTestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase",
"ContextRestarted:ContextHierarchyLevel2TestCase",
"ContextRefreshed:ContextHierarchyLevel3bTestCase",
"AfterTestClass:ContextHierarchyLevel3bTestCase",
- "ContextStopped:ContextHierarchyLevel3bTestCase",
- "ContextStopped:ContextHierarchyLevel2TestCase",
- "ContextStopped:ContextHierarchyLevel1TestCase"
+ "ContextPaused:ContextHierarchyLevel3bTestCase",
+ "ContextPaused:ContextHierarchyLevel2TestCase",
+ "ContextPaused:ContextHierarchyLevel1TestCase"
);
}