Skip to content

Commit 149d468

Browse files
committed
Introduce ConfigurableApplicationContext.pause() and SmartLifecycle.isPauseable()
Closes gh-35269
1 parent 96bc1f5 commit 149d468

File tree

12 files changed

+191
-53
lines changed

12 files changed

+191
-53
lines changed

spring-context-support/src/test/java/org/springframework/scheduling/quartz/QuartzSupportTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ void schedulerWithHsqlDataSource() {
391391
try (ClassPathXmlApplicationContext ctx = context("databasePersistence.xml")) {
392392
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
393393
assertThat(jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty()).as("No triggers were persisted").isFalse();
394-
ctx.stop();
395394
ctx.restart();
396395
}
397396
}

spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,27 @@ public interface ConfigurableApplicationContext extends ApplicationContext, Life
221221
void refresh() throws BeansException, IllegalStateException;
222222

223223
/**
224-
* Stop all beans in this application context if necessary, and subsequently
224+
* Pause all beans in this application context if necessary, and subsequently
225225
* restart all auto-startup beans, effectively restoring the lifecycle state
226226
* after {@link #refresh()} (typically after a preceding {@link #stop()} call
227227
* when a full {@link #start()} of even lazy-starting beans is to be avoided).
228228
* @since 7.0
229-
* @see #stop()
229+
* @see #pause()
230+
* @see #start()
230231
* @see SmartLifecycle#isAutoStartup()
231232
*/
232233
void restart();
233234

235+
/**
236+
* Stop all beans in this application context unless they explicitly opt out of
237+
* pausing through {@link SmartLifecycle#isPauseable()} returning {@code false}.
238+
* @since 7.0
239+
* @see #restart()
240+
* @see #stop()
241+
* @see SmartLifecycle#isPauseable()
242+
*/
243+
void pause();
244+
234245
/**
235246
* Register a shutdown hook with the JVM runtime, closing this context
236247
* on JVM shutdown unless it has already been closed at that time.

spring-context/src/main/java/org/springframework/context/LifecycleProcessor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ default void onRestart() {
4444
start();
4545
}
4646

47+
/**
48+
* Notification of context pause for auto-stopping components.
49+
* @since 7.0
50+
* @see ConfigurableApplicationContext#pause()
51+
*/
52+
default void onPause() {
53+
stop();
54+
}
55+
4756
/**
4857
* Notification of context close phase for auto-stopping components
4958
* before destruction.

spring-context/src/main/java/org/springframework/context/SmartLifecycle.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,43 @@ public interface SmartLifecycle extends Lifecycle, Phased {
8585
/**
8686
* Returns {@code true} if this {@code Lifecycle} component should get
8787
* started automatically by the container at the time that the containing
88-
* {@link ApplicationContext} gets refreshed.
88+
* {@link ApplicationContext} gets refreshed or restarted.
8989
* <p>A value of {@code false} indicates that the component is intended to
9090
* be started through an explicit {@link #start()} call instead, analogous
9191
* to a plain {@link Lifecycle} implementation.
9292
* <p>The default implementation returns {@code true}.
9393
* @see #start()
9494
* @see #getPhase()
9595
* @see LifecycleProcessor#onRefresh()
96+
* @see LifecycleProcessor#onRestart()
9697
* @see ConfigurableApplicationContext#refresh()
98+
* @see ConfigurableApplicationContext#restart()
9799
*/
98100
default boolean isAutoStartup() {
99101
return true;
100102
}
101103

104+
/**
105+
* Returns {@code true} if this {@code Lifecycle} component is able to
106+
* participate in a restart sequence, receiving corresponding {@link #stop()}
107+
* and {@link #start()} calls with a potential pause in-between.
108+
* <p>A value of {@code false} indicates that the component prefers to
109+
* be skipped in a pause scenario, neither receiving a {@link #stop()}
110+
* call nor a subsequent {@link #start()} call, analogous to a plain
111+
* {@link Lifecycle} implementation. It will only receive a {@link #stop()}
112+
* call on close and on explicit context-wide stopping but not on pause.
113+
* <p>The default implementation returns {@code true}.
114+
* @since 7.0
115+
* @see #stop()
116+
* @see LifecycleProcessor#onPause()
117+
* @see LifecycleProcessor#onClose()
118+
* @see ConfigurableApplicationContext#pause()
119+
* @see ConfigurableApplicationContext#close()
120+
*/
121+
default boolean isPauseable() {
122+
return true;
123+
}
124+
102125
/**
103126
* Indicates that a Lifecycle component must stop if it is currently running.
104127
* <p>The provided callback is used by the {@link LifecycleProcessor} to support
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.event;
18+
19+
import org.springframework.context.ApplicationContext;
20+
import org.springframework.context.ConfigurableApplicationContext;
21+
22+
/**
23+
* Event raised when an {@code ApplicationContext} gets paused.
24+
*
25+
* <p>Note that {@code ContextPausedEvent} is a specialization of
26+
* {@link ContextStoppedEvent}.
27+
*
28+
* @author Juergen Hoeller
29+
* @since 7.0
30+
* @see ConfigurableApplicationContext#pause()
31+
* @see ContextRestartedEvent
32+
* @see ContextStoppedEvent
33+
*/
34+
@SuppressWarnings("serial")
35+
public class ContextPausedEvent extends ContextStoppedEvent {
36+
37+
/**
38+
* Create a new {@code ContextRestartedEvent}.
39+
* @param source the {@code ContextPausedEvent} that has been restarted
40+
* (must not be {@code null})
41+
*/
42+
public ContextPausedEvent(ApplicationContext source) {
43+
super(source);
44+
}
45+
46+
}

spring-context/src/main/java/org/springframework/context/event/ContextRestartedEvent.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.context.event;
1818

1919
import org.springframework.context.ApplicationContext;
20+
import org.springframework.context.ConfigurableApplicationContext;
2021

2122
/**
2223
* Event raised when an {@code ApplicationContext} gets restarted.
@@ -26,8 +27,9 @@
2627
*
2728
* @author Sam Brannen
2829
* @since 7.0
30+
* @see ConfigurableApplicationContext#restart()
31+
* @see ContextPausedEvent
2932
* @see ContextStartedEvent
30-
* @see ContextStoppedEvent
3133
*/
3234
@SuppressWarnings("serial")
3335
public class ContextRestartedEvent extends ContextStartedEvent {

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.springframework.context.ResourceLoaderAware;
6767
import org.springframework.context.event.ApplicationEventMulticaster;
6868
import org.springframework.context.event.ContextClosedEvent;
69+
import org.springframework.context.event.ContextPausedEvent;
6970
import org.springframework.context.event.ContextRefreshedEvent;
7071
import org.springframework.context.event.ContextRestartedEvent;
7172
import org.springframework.context.event.ContextStartedEvent;
@@ -1555,6 +1556,12 @@ public void restart() {
15551556
publishEvent(new ContextRestartedEvent(this));
15561557
}
15571558

1559+
@Override
1560+
public void pause() {
1561+
getLifecycleProcessor().onPause();
1562+
publishEvent(new ContextPausedEvent(this));
1563+
}
1564+
15581565
@Override
15591566
public boolean isRunning() {
15601567
return (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning());

spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ public void start() {
287287
*/
288288
@Override
289289
public void stop() {
290-
stopBeans();
290+
stopBeans(false);
291291
this.running = false;
292292
}
293293

@@ -308,7 +308,7 @@ public void onRefresh() {
308308
catch (ApplicationContextException ex) {
309309
// Some bean failed to auto-start within context refresh:
310310
// stop already started beans on context refresh failure.
311-
stopBeans();
311+
stopBeans(false);
312312
throw ex;
313313
}
314314
this.running = true;
@@ -318,15 +318,23 @@ public void onRefresh() {
318318
public void onRestart() {
319319
this.stoppedBeans = null;
320320
if (this.running) {
321-
stopBeans();
321+
stopBeans(true);
322322
}
323323
startBeans(true);
324324
this.running = true;
325325
}
326326

327+
@Override
328+
public void onPause() {
329+
if (this.running) {
330+
stopBeans(true);
331+
this.running = false;
332+
}
333+
}
334+
327335
@Override
328336
public void onClose() {
329-
stopBeans();
337+
stopBeans(false);
330338
this.running = false;
331339
}
332340

@@ -341,7 +349,7 @@ public boolean isRunning() {
341349
void stopForRestart() {
342350
if (this.running) {
343351
this.stoppedBeans = ConcurrentHashMap.newKeySet();
344-
stopBeans();
352+
stopBeans(false);
345353
this.running = false;
346354
}
347355
}
@@ -361,7 +369,8 @@ private void startBeans(boolean autoStartupOnly) {
361369
lifecycleBeans.forEach((beanName, bean) -> {
362370
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
363371
int startupPhase = getPhase(bean);
364-
phases.computeIfAbsent(startupPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, autoStartupOnly))
372+
phases.computeIfAbsent(
373+
startupPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, autoStartupOnly, false))
365374
.add(beanName, bean);
366375
}
367376
});
@@ -424,13 +433,14 @@ private boolean toBeStarted(String beanName, Lifecycle bean) {
424433
(!(bean instanceof SmartLifecycle smartLifecycle) || smartLifecycle.isAutoStartup()));
425434
}
426435

427-
private void stopBeans() {
436+
private void stopBeans(boolean pauseableOnly) {
428437
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
429438
Map<Integer, LifecycleGroup> phases = new TreeMap<>(Comparator.reverseOrder());
430439

431440
lifecycleBeans.forEach((beanName, bean) -> {
432441
int shutdownPhase = getPhase(bean);
433-
phases.computeIfAbsent(shutdownPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, false))
442+
phases.computeIfAbsent(
443+
shutdownPhase, phase -> new LifecycleGroup(phase, lifecycleBeans, false, pauseableOnly))
434444
.add(beanName, bean);
435445
});
436446

@@ -446,13 +456,13 @@ private void stopBeans() {
446456
* @param beanName the name of the bean to stop
447457
*/
448458
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
449-
final CountDownLatch latch, final Set<String> countDownBeanNames) {
459+
boolean pauseableOnly, final CountDownLatch latch, final Set<String> countDownBeanNames) {
450460

451461
Lifecycle bean = lifecycleBeans.remove(beanName);
452462
if (bean != null) {
453463
String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
454464
for (String dependentBean : dependentBeans) {
455-
doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
465+
doStop(lifecycleBeans, dependentBean, pauseableOnly, latch, countDownBeanNames);
456466
}
457467
try {
458468
if (bean.isRunning()) {
@@ -461,20 +471,22 @@ private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final Strin
461471
stoppedBeans.add(beanName);
462472
}
463473
if (bean instanceof SmartLifecycle smartLifecycle) {
464-
if (logger.isTraceEnabled()) {
465-
logger.trace("Asking bean '" + beanName + "' of type [" +
466-
bean.getClass().getName() + "] to stop");
467-
}
468-
countDownBeanNames.add(beanName);
469-
smartLifecycle.stop(() -> {
470-
latch.countDown();
471-
countDownBeanNames.remove(beanName);
472-
if (logger.isDebugEnabled()) {
473-
logger.debug("Bean '" + beanName + "' completed its stop procedure");
474+
if (!pauseableOnly || smartLifecycle.isPauseable()) {
475+
if (logger.isTraceEnabled()) {
476+
logger.trace("Asking bean '" + beanName + "' of type [" +
477+
bean.getClass().getName() + "] to stop");
474478
}
475-
});
479+
countDownBeanNames.add(beanName);
480+
smartLifecycle.stop(() -> {
481+
latch.countDown();
482+
countDownBeanNames.remove(beanName);
483+
if (logger.isDebugEnabled()) {
484+
logger.debug("Bean '" + beanName + "' completed its stop procedure");
485+
}
486+
});
487+
}
476488
}
477-
else {
489+
else if (!pauseableOnly) {
478490
if (logger.isTraceEnabled()) {
479491
logger.trace("Stopping bean '" + beanName + "' of type [" +
480492
bean.getClass().getName() + "]");
@@ -562,14 +574,19 @@ private class LifecycleGroup {
562574

563575
private final boolean autoStartupOnly;
564576

577+
private final boolean pauseableOnly;
578+
565579
private final List<LifecycleGroupMember> members = new ArrayList<>();
566580

567581
private int smartMemberCount;
568582

569-
public LifecycleGroup(int phase, Map<String, ? extends Lifecycle> lifecycleBeans, boolean autoStartupOnly) {
583+
public LifecycleGroup(int phase, Map<String, ? extends Lifecycle> lifecycleBeans,
584+
boolean autoStartupOnly, boolean pauseableOnly) {
585+
570586
this.phase = phase;
571587
this.lifecycleBeans = lifecycleBeans;
572588
this.autoStartupOnly = autoStartupOnly;
589+
this.pauseableOnly = pauseableOnly;
573590
}
574591

575592
public void add(String name, Lifecycle bean) {
@@ -621,7 +638,7 @@ public void stop() {
621638
Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet());
622639
for (LifecycleGroupMember member : this.members) {
623640
if (lifecycleBeanNames.contains(member.name)) {
624-
doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames);
641+
doStop(this.lifecycleBeans, member.name, this.pauseableOnly, latch, countDownBeanNames);
625642
}
626643
else if (member.bean instanceof SmartLifecycle) {
627644
// Already removed: must have been a dependent bean from another phase

0 commit comments

Comments
 (0)