mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Introduce ConfigurableApplicationContext.pause() and SmartLifecycle.isPauseable()
Closes gh-35269
This commit is contained in:
+13
-2
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
* <p>The provided callback is used by the {@link LifecycleProcessor} to support
|
||||
|
||||
+46
@@ -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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-1
@@ -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 {
|
||||
|
||||
+7
@@ -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());
|
||||
|
||||
+41
-24
@@ -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<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
|
||||
Map<Integer, LifecycleGroup> phases = new TreeMap<>(Comparator.reverseOrder());
|
||||
|
||||
lifecycleBeans.forEach((beanName, bean) -> {
|
||||
int shutdownPhase = getPhase(bean);
|
||||
phases.computeIfAbsent(shutdownPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, false))
|
||||
phases.computeIfAbsent(
|
||||
shutdownPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, false, pauseableOnly))
|
||||
.add(beanName, bean);
|
||||
});
|
||||
|
||||
@@ -446,13 +456,13 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
* @param beanName the name of the bean to stop
|
||||
*/
|
||||
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
|
||||
final CountDownLatch latch, final Set<String> countDownBeanNames) {
|
||||
boolean pauseableOnly, final CountDownLatch latch, final Set<String> countDownBeanNames) {
|
||||
|
||||
Lifecycle bean = lifecycleBeans.remove(beanName);
|
||||
if (bean != null) {
|
||||
String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
|
||||
for (String dependentBean : dependentBeans) {
|
||||
doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
|
||||
doStop(lifecycleBeans, dependentBean, pauseableOnly, latch, countDownBeanNames);
|
||||
}
|
||||
try {
|
||||
if (bean.isRunning()) {
|
||||
@@ -461,20 +471,22 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
stoppedBeans.add(beanName);
|
||||
}
|
||||
if (bean instanceof SmartLifecycle smartLifecycle) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Asking bean '" + beanName + "' of type [" +
|
||||
bean.getClass().getName() + "] to stop");
|
||||
}
|
||||
countDownBeanNames.add(beanName);
|
||||
smartLifecycle.stop(() -> {
|
||||
latch.countDown();
|
||||
countDownBeanNames.remove(beanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean '" + beanName + "' completed its stop procedure");
|
||||
if (!pauseableOnly || smartLifecycle.isPauseable()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Asking bean '" + beanName + "' of type [" +
|
||||
bean.getClass().getName() + "] to stop");
|
||||
}
|
||||
});
|
||||
countDownBeanNames.add(beanName);
|
||||
smartLifecycle.stop(() -> {
|
||||
latch.countDown();
|
||||
countDownBeanNames.remove(beanName);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean '" + beanName + "' completed its stop procedure");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (!pauseableOnly) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Stopping bean '" + beanName + "' of type [" +
|
||||
bean.getClass().getName() + "]");
|
||||
@@ -562,14 +574,19 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
|
||||
private final boolean autoStartupOnly;
|
||||
|
||||
private final boolean pauseableOnly;
|
||||
|
||||
private final List<LifecycleGroupMember> members = new ArrayList<>();
|
||||
|
||||
private int smartMemberCount;
|
||||
|
||||
public LifecycleGroup(int phase, Map<String, ? extends Lifecycle> lifecycleBeans, boolean autoStartupOnly) {
|
||||
public LifecycleGroup(int phase, Map<String, ? extends Lifecycle> lifecycleBeans,
|
||||
boolean autoStartupOnly, boolean pauseableOnly) {
|
||||
|
||||
this.phase = phase;
|
||||
this.lifecycleBeans = lifecycleBeans;
|
||||
this.autoStartupOnly = autoStartupOnly;
|
||||
this.pauseableOnly = pauseableOnly;
|
||||
}
|
||||
|
||||
public void add(String name, Lifecycle bean) {
|
||||
@@ -621,7 +638,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet());
|
||||
for (LifecycleGroupMember member : this.members) {
|
||||
if (lifecycleBeanNames.contains(member.name)) {
|
||||
doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
|
||||
doStop(this.lifecycleBeans, member.name, this.pauseableOnly, latch, countDownBeanNames);
|
||||
}
|
||||
else if (member.bean instanceof SmartLifecycle) {
|
||||
// Already removed: must have been a dependent bean from another phase
|
||||
|
||||
Reference in New Issue
Block a user