Skip to content

Commit 907794f

Browse files
committed
Open DefaultLifecycleProcessor for extension
Includes a demonstration ConcurrentLifecycleProcessor class As per spring-projectsgh-34634
1 parent 02c7719 commit 907794f

File tree

2 files changed

+106
-15
lines changed

2 files changed

+106
-15
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.springframework.context.support;
2+
3+
import org.apache.commons.logging.Log;
4+
import org.apache.commons.logging.LogFactory;
5+
import org.springframework.context.Lifecycle;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.TreeMap;
10+
import java.util.concurrent.*;
11+
12+
/**
13+
* An example extension of Spring's {@link DefaultLifecycleProcessor}.
14+
* </p>
15+
* All Lifecycle beans of a same phase start _at once_ rather than sequentially.
16+
* Phases still only complete after every bean has returned from start().
17+
* </p>
18+
* A start timeout can be specified for each phase; the default timeout is 10 seconds.
19+
* If any timeout is exceeded, an exception is thrown and the application context refresh() fails.
20+
* </p>
21+
* @author Francis Lalonde
22+
*/
23+
public class ConcurrentLifecycleProcessor extends DefaultLifecycleProcessor {
24+
25+
private final Map<Integer, Long> timeoutsForStartPhases = new ConcurrentHashMap<>();
26+
27+
private final long timeoutPerStartPhase = 10000;
28+
29+
public void setTimeoutsForStartPhases(Map<Integer, Long> timeoutsForShutdownPhases) {
30+
this.timeoutsForStartPhases.putAll(timeoutsForShutdownPhases);
31+
}
32+
33+
protected long determineStartTimeout(int phase) {
34+
Long timeout = this.timeoutsForStartPhases.get(phase);
35+
return (timeout != null ? timeout : this.timeoutPerStartPhase);
36+
}
37+
38+
@Override
39+
protected void startBeans(boolean autoStartupOnly) {
40+
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
41+
Map<Integer, ConcurentLifecycleGroup> phases = new TreeMap<>();
42+
43+
lifecycleBeans.forEach((beanName, bean) -> {
44+
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
45+
int startupPhase = getPhase(bean);
46+
phases.computeIfAbsent(startupPhase,
47+
phase -> new ConcurentLifecycleGroup(phase, determineStartTimeout(phase), determineStopTimeout(phase), lifecycleBeans, autoStartupOnly)
48+
).add(beanName, bean);
49+
}
50+
});
51+
52+
if (!phases.isEmpty()) {
53+
phases.values().forEach(ConcurentLifecycleGroup::start);
54+
}
55+
}
56+
57+
protected class ConcurentLifecycleGroup extends LifecycleGroup {
58+
59+
private final Log logger = LogFactory.getLog(getClass());
60+
61+
private final long startTimeout;
62+
63+
public ConcurentLifecycleGroup(int phase, long startTimeout, long stopTimeout, Map<String, ? extends Lifecycle> lifecycleBeans, boolean autoStartupOnly) {
64+
super(phase, stopTimeout, lifecycleBeans, autoStartupOnly);
65+
this.startTimeout = startTimeout;
66+
}
67+
68+
@Override
69+
public void start() {
70+
if (this.members.isEmpty()) {
71+
return;
72+
}
73+
if (logger.isDebugEnabled()) {
74+
logger.debug("Starting beans in phase " + phase);
75+
}
76+
77+
List<CompletableFuture<Void>> starting = members.stream()
78+
.map(member -> CompletableFuture.runAsync(() -> doStart(lifecycleBeans, member.name(), autoStartupOnly)))
79+
.toList();
80+
81+
try {
82+
CompletableFuture.allOf(starting.toArray(CompletableFuture<?>[]::new))
83+
.get(startTimeout, TimeUnit.MILLISECONDS);
84+
} catch (TimeoutException e) {
85+
throw new IllegalStateException("Timeout exceeded starting beans in phase " + this.phase, e);
86+
} catch (InterruptedException | ExecutionException e) {
87+
throw new IllegalStateException("Error starting beans in phase " + this.phase, e);
88+
}
89+
}
90+
}
91+
}

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) {
168168
this.timeoutPerShutdownPhase = timeoutPerShutdownPhase;
169169
}
170170

171-
private long determineTimeout(int phase) {
171+
protected long determineStopTimeout(int phase) {
172172
Long timeout = this.timeoutsForShutdownPhases.get(phase);
173173
return (timeout != null ? timeout : this.timeoutPerShutdownPhase);
174174
}
@@ -182,7 +182,7 @@ public void setBeanFactory(BeanFactory beanFactory) {
182182
this.beanFactory = clbf;
183183
}
184184

185-
private ConfigurableListableBeanFactory getBeanFactory() {
185+
protected ConfigurableListableBeanFactory getBeanFactory() {
186186
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
187187
Assert.state(beanFactory != null, "No BeanFactory available");
188188
return beanFactory;
@@ -275,15 +275,15 @@ void restartAfterStop() {
275275
}
276276
}
277277

278-
private void startBeans(boolean autoStartupOnly) {
278+
protected void startBeans(boolean autoStartupOnly) {
279279
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
280280
Map<Integer, LifecycleGroup> phases = new TreeMap<>();
281281

282282
lifecycleBeans.forEach((beanName, bean) -> {
283283
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
284284
int startupPhase = getPhase(bean);
285285
phases.computeIfAbsent(startupPhase,
286-
phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, autoStartupOnly)
286+
phase -> new LifecycleGroup(phase, determineStopTimeout(phase), lifecycleBeans, autoStartupOnly)
287287
).add(beanName, bean);
288288
}
289289
});
@@ -293,7 +293,7 @@ private void startBeans(boolean autoStartupOnly) {
293293
}
294294
}
295295

296-
private boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
296+
protected boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
297297
Set<String> stoppedBeans = this.stoppedBeans;
298298
return (stoppedBeans != null ? stoppedBeans.contains(beanName) :
299299
(bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup()));
@@ -305,7 +305,7 @@ private boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
305305
* @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value
306306
* @param beanName the name of the bean to start
307307
*/
308-
private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
308+
protected void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
309309
Lifecycle bean = lifecycleBeans.remove(beanName);
310310
if (bean != null && bean != this) {
311311
String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
@@ -342,7 +342,7 @@ private void stopBeans() {
342342
lifecycleBeans.forEach((beanName, bean) -> {
343343
int shutdownPhase = getPhase(bean);
344344
phases.computeIfAbsent(shutdownPhase,
345-
phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, false)
345+
phase -> new LifecycleGroup(phase, determineStopTimeout(phase), lifecycleBeans, false)
346346
).add(beanName, bean);
347347
});
348348

@@ -357,7 +357,7 @@ private void stopBeans() {
357357
* @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value
358358
* @param beanName the name of the bean to stop
359359
*/
360-
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
360+
protected void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
361361
final CountDownLatch latch, final Set<String> countDownBeanNames) {
362362

363363
Lifecycle bean = lifecycleBeans.remove(beanName);
@@ -466,17 +466,17 @@ protected int getPhase(Lifecycle bean) {
466466
* The group is expected to be created in an ad-hoc fashion and group members are
467467
* expected to always have the same 'phase' value.
468468
*/
469-
private class LifecycleGroup {
469+
protected class LifecycleGroup {
470470

471-
private final int phase;
471+
protected final int phase;
472472

473-
private final long timeout;
473+
protected final long timeout;
474474

475-
private final Map<String, ? extends Lifecycle> lifecycleBeans;
475+
protected final Map<String, ? extends Lifecycle> lifecycleBeans;
476476

477-
private final boolean autoStartupOnly;
477+
protected final boolean autoStartupOnly;
478478

479-
private final List<LifecycleGroupMember> members = new ArrayList<>();
479+
protected final List<LifecycleGroupMember> members = new ArrayList<>();
480480

481481
private int smartMemberCount;
482482

@@ -545,7 +545,7 @@ else if (member.bean instanceof SmartLifecycle) {
545545
/**
546546
* A simple record of a LifecycleGroup member.
547547
*/
548-
private record LifecycleGroupMember(String name, Lifecycle bean) {}
548+
protected record LifecycleGroupMember(String name, Lifecycle bean) {}
549549

550550

551551
/**

0 commit comments

Comments
 (0)