From 86359333f25070da4bebd2f14643da2957ff90f2 Mon Sep 17 00:00:00 2001 From: eyealike Date: Fri, 23 Dec 2011 17:43:49 -0800 Subject: [PATCH] SPR-8941: Optionally enforce phase over dependency ordering during lifecycle processing --- .../support/DefaultLifecycleProcessor.java | 36 ++++++++++-- .../DefaultLifecycleProcessorTests.java | 57 +++++++++++++++---- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java index 025f4cfb96ca..cced04612b00 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java @@ -58,6 +58,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor private volatile ConfigurableListableBeanFactory beanFactory; + private volatile boolean ignorePhaseOfDependencies = true; /** * Specify the maximum time allotted in milliseconds for the shutdown of @@ -68,6 +69,21 @@ public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) { this.timeoutPerShutdownPhase = timeoutPerShutdownPhase; } + /** + * Specifies whether a lifecycle bean which other lifecycle beans depend on + * (either explicitly or via {@link DependsOn}) } should be started + * (stopped) before (after) its dependents, irrespective of their phase. The + * backwards-compatible default is true, a dependency bean is always started + * (stopped) before (after) the beans that depend on it even if its + * {@link Phased#getPhase()} method indicates that it should be started + * later (earlier). If false, lifecycle beans are never started or stopped + * in an order that disregards their phase. + */ + public void setIgnorePhaseOfDependencies( final boolean ignorePhaseOfDependencies ) + { + this.ignorePhaseOfDependencies = ignorePhaseOfDependencies; + } + public void setBeanFactory(BeanFactory beanFactory) { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; @@ -154,9 +170,13 @@ private void startBeans(boolean autoStartupOnly) { private void doStart(Map lifecycleBeans, String beanName, boolean autoStartupOnly) { Lifecycle bean = lifecycleBeans.remove(beanName); if (bean != null && !this.equals(bean)) { - String[] dependenciesForBean = this.beanFactory.getDependenciesForBean(beanName); - for (String dependency : dependenciesForBean) { - doStart(lifecycleBeans, dependency, autoStartupOnly); + final String[] dependencies = this.beanFactory.getDependenciesForBean(beanName); + final int currentPhase = getPhase( bean ); + for (final String dependency : dependencies) { + final Lifecycle dependencyBean = lifecycleBeans.get( dependency ); + if( ignorePhaseOfDependencies || dependencyBean == null || getPhase( dependencyBean ) <= currentPhase ) { + doStart(lifecycleBeans, dependency, autoStartupOnly); + } } if (!bean.isRunning() && (!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle) bean).isAutoStartup())) { @@ -209,9 +229,13 @@ private void doStop(Map lifecycleBeans, final Strin Lifecycle bean = lifecycleBeans.remove(beanName); if (bean != null) { - String[] dependentBeans = this.beanFactory.getDependentBeans(beanName); - for (String dependentBean : dependentBeans) { - doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames); + final String[] dependents = this.beanFactory.getDependentBeans(beanName); + final int currentPhase = getPhase( bean ); + for (final String dependent : dependents) { + final Lifecycle dependentBean = lifecycleBeans.get( dependent ); + if( ignorePhaseOfDependencies || dependentBean == null || getPhase( dependentBean ) >= currentPhase ) { + doStop(lifecycleBeans, dependent, latch, countDownBeanNames); + } } try { if (bean.isRunning()) { diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java index 18bd081d2dfa..4fa691fcb3e8 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/support/DefaultLifecycleProcessorTests.java @@ -358,6 +358,15 @@ public void mixedShutdown() throws Exception { @Test public void dependencyStartedFirstEvenIfItsPhaseIsHigher() throws Exception { + impactOfDependencyAndPhaseOnStartupOrder(true); + } + + @Test + public void dependencyStartedLastIfItsPhaseIsHigher() throws Exception { + impactOfDependencyAndPhaseOnStartupOrder(false); + } + + private void impactOfDependencyAndPhaseOnStartupOrder(final boolean ignorePhaseOfDependencies) { CopyOnWriteArrayList startedBeans = new CopyOnWriteArrayList(); TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans); TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forStartupTests(2, startedBeans); @@ -369,6 +378,11 @@ public void dependencyStartedFirstEvenIfItsPhaseIsHigher() throws Exception { context.getBeanFactory().registerSingleton("bean99", bean99); context.getBeanFactory().registerSingleton("beanMax", beanMax); context.getBeanFactory().registerDependentBean("bean99", "bean2"); + + final BeanDefinition beanDefinition = new RootBeanDefinition(DefaultLifecycleProcessor.class); + beanDefinition.getPropertyValues().addPropertyValue("ignorePhaseOfDependencies", ignorePhaseOfDependencies); + context.registerBeanDefinition("lifecycleProcessor", beanDefinition); + context.refresh(); assertTrue(beanMin.isRunning()); assertTrue(bean2.isRunning()); @@ -376,16 +390,25 @@ public void dependencyStartedFirstEvenIfItsPhaseIsHigher() throws Exception { assertTrue(beanMax.isRunning()); assertEquals(4, startedBeans.size()); assertEquals(Integer.MIN_VALUE, getPhase(startedBeans.get(0))); - assertEquals(99, getPhase(startedBeans.get(1))); - assertEquals(bean99, startedBeans.get(1)); - assertEquals(2, getPhase(startedBeans.get(2))); - assertEquals(bean2, startedBeans.get(2)); + assertEquals(ignorePhaseOfDependencies ? 99 : 2, getPhase(startedBeans.get(1))); + assertEquals(ignorePhaseOfDependencies ? bean99 : bean2, startedBeans.get(1)); + assertEquals(ignorePhaseOfDependencies ? 2 : 99, getPhase(startedBeans.get(2))); + assertEquals(ignorePhaseOfDependencies ? bean2 : bean99, startedBeans.get(2)); assertEquals(Integer.MAX_VALUE, getPhase(startedBeans.get(3))); context.stop(); } @Test public void dependentShutdownFirstEvenIfItsPhaseIsLower() throws Exception { + impactOfDependencyAndPhaseOnShutdownOrder(true); + } + + @Test + public void dependentShutdownLastIfItsPhaseIsLower() throws Exception { + impactOfDependencyAndPhaseOnShutdownOrder(false); + } + + private void impactOfDependencyAndPhaseOnShutdownOrder(final boolean ignorePhaseOfDependencies) { CopyOnWriteArrayList stoppedBeans = new CopyOnWriteArrayList(); TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 100, stoppedBeans); TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans); @@ -401,7 +424,12 @@ public void dependentShutdownFirstEvenIfItsPhaseIsLower() throws Exception { context.getBeanFactory().registerSingleton("bean99", bean99); context.getBeanFactory().registerSingleton("beanMax", beanMax); context.getBeanFactory().registerDependentBean("bean99", "bean2"); + + final BeanDefinition beanDefinition = new RootBeanDefinition(DefaultLifecycleProcessor.class); + beanDefinition.getPropertyValues().addPropertyValue("ignorePhaseOfDependencies", ignorePhaseOfDependencies); + context.registerBeanDefinition("lifecycleProcessor", beanDefinition); context.refresh(); + assertTrue(beanMin.isRunning()); assertTrue(bean1.isRunning()); assertTrue(bean2.isRunning()); @@ -417,12 +445,21 @@ public void dependentShutdownFirstEvenIfItsPhaseIsLower() throws Exception { assertFalse(beanMax.isRunning()); assertEquals(6, stoppedBeans.size()); assertEquals(Integer.MAX_VALUE, getPhase(stoppedBeans.get(0))); - assertEquals(2, getPhase(stoppedBeans.get(1))); - assertEquals(bean2, stoppedBeans.get(1)); - assertEquals(99, getPhase(stoppedBeans.get(2))); - assertEquals(bean99, stoppedBeans.get(2)); - assertEquals(7, getPhase(stoppedBeans.get(3))); - assertEquals(1, getPhase(stoppedBeans.get(4))); + if (ignorePhaseOfDependencies) { + assertEquals(2, getPhase(stoppedBeans.get(1))); + assertEquals(bean2, stoppedBeans.get(1)); + assertEquals(99, getPhase(stoppedBeans.get(2))); + assertEquals(bean99, stoppedBeans.get(2)); + assertEquals(7, getPhase(stoppedBeans.get(3))); + assertEquals(1, getPhase(stoppedBeans.get(4))); + } else { + assertEquals(99, getPhase(stoppedBeans.get(1))); + assertEquals(bean99, stoppedBeans.get(1)); + assertEquals(7, getPhase(stoppedBeans.get(2))); + assertEquals(bean7, stoppedBeans.get(2)); + assertEquals(2, getPhase(stoppedBeans.get(3))); + assertEquals(1, getPhase(stoppedBeans.get(4))); + } assertEquals(Integer.MIN_VALUE, getPhase(stoppedBeans.get(5))); }