001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.context.support; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.LinkedHashMap; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.concurrent.CountDownLatch; 028import java.util.concurrent.TimeUnit; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032 033import org.springframework.beans.factory.BeanFactory; 034import org.springframework.beans.factory.BeanFactoryAware; 035import org.springframework.beans.factory.BeanFactoryUtils; 036import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 037import org.springframework.context.ApplicationContextException; 038import org.springframework.context.Lifecycle; 039import org.springframework.context.LifecycleProcessor; 040import org.springframework.context.Phased; 041import org.springframework.context.SmartLifecycle; 042 043/** 044 * Default implementation of the {@link LifecycleProcessor} strategy. 045 * 046 * @author Mark Fisher 047 * @author Juergen Hoeller 048 * @since 3.0 049 */ 050public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware { 051 052 private final Log logger = LogFactory.getLog(getClass()); 053 054 private volatile long timeoutPerShutdownPhase = 30000; 055 056 private volatile boolean running; 057 058 private volatile ConfigurableListableBeanFactory beanFactory; 059 060 061 /** 062 * Specify the maximum time allotted in milliseconds for the shutdown of 063 * any phase (group of SmartLifecycle beans with the same 'phase' value). 064 * <p>The default value is 30 seconds. 065 */ 066 public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) { 067 this.timeoutPerShutdownPhase = timeoutPerShutdownPhase; 068 } 069 070 @Override 071 public void setBeanFactory(BeanFactory beanFactory) { 072 if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { 073 throw new IllegalArgumentException( 074 "DefaultLifecycleProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); 075 } 076 this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; 077 } 078 079 080 // Lifecycle implementation 081 082 /** 083 * Start all registered beans that implement {@link Lifecycle} and are <i>not</i> 084 * already running. Any bean that implements {@link SmartLifecycle} will be 085 * started within its 'phase', and all phases will be ordered from lowest to 086 * highest value. All beans that do not implement {@link SmartLifecycle} will be 087 * started in the default phase 0. A bean declared as a dependency of another bean 088 * will be started before the dependent bean regardless of the declared phase. 089 */ 090 @Override 091 public void start() { 092 startBeans(false); 093 this.running = true; 094 } 095 096 /** 097 * Stop all registered beans that implement {@link Lifecycle} and <i>are</i> 098 * currently running. Any bean that implements {@link SmartLifecycle} will be 099 * stopped within its 'phase', and all phases will be ordered from highest to 100 * lowest value. All beans that do not implement {@link SmartLifecycle} will be 101 * stopped in the default phase 0. A bean declared as dependent on another bean 102 * will be stopped before the dependency bean regardless of the declared phase. 103 */ 104 @Override 105 public void stop() { 106 stopBeans(); 107 this.running = false; 108 } 109 110 @Override 111 public void onRefresh() { 112 startBeans(true); 113 this.running = true; 114 } 115 116 @Override 117 public void onClose() { 118 stopBeans(); 119 this.running = false; 120 } 121 122 @Override 123 public boolean isRunning() { 124 return this.running; 125 } 126 127 128 // Internal helpers 129 130 private void startBeans(boolean autoStartupOnly) { 131 Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); 132 Map<Integer, LifecycleGroup> phases = new HashMap<Integer, LifecycleGroup>(); 133 for (Map.Entry<String, ? extends Lifecycle> entry : lifecycleBeans.entrySet()) { 134 Lifecycle bean = entry.getValue(); 135 if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) { 136 int phase = getPhase(bean); 137 LifecycleGroup group = phases.get(phase); 138 if (group == null) { 139 group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly); 140 phases.put(phase, group); 141 } 142 group.add(entry.getKey(), bean); 143 } 144 } 145 if (!phases.isEmpty()) { 146 List<Integer> keys = new ArrayList<Integer>(phases.keySet()); 147 Collections.sort(keys); 148 for (Integer key : keys) { 149 phases.get(key).start(); 150 } 151 } 152 } 153 154 /** 155 * Start the specified bean as part of the given set of Lifecycle beans, 156 * making sure that any beans that it depends on are started first. 157 * @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value 158 * @param beanName the name of the bean to start 159 */ 160 private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) { 161 Lifecycle bean = lifecycleBeans.remove(beanName); 162 if (bean != null && bean != this) { 163 String[] dependenciesForBean = this.beanFactory.getDependenciesForBean(beanName); 164 for (String dependency : dependenciesForBean) { 165 doStart(lifecycleBeans, dependency, autoStartupOnly); 166 } 167 if (!bean.isRunning() && 168 (!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle) bean).isAutoStartup())) { 169 if (logger.isDebugEnabled()) { 170 logger.debug("Starting bean '" + beanName + "' of type [" + bean.getClass().getName() + "]"); 171 } 172 try { 173 bean.start(); 174 } 175 catch (Throwable ex) { 176 throw new ApplicationContextException("Failed to start bean '" + beanName + "'", ex); 177 } 178 if (logger.isDebugEnabled()) { 179 logger.debug("Successfully started bean '" + beanName + "'"); 180 } 181 } 182 } 183 } 184 185 private void stopBeans() { 186 Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); 187 Map<Integer, LifecycleGroup> phases = new HashMap<Integer, LifecycleGroup>(); 188 for (Map.Entry<String, Lifecycle> entry : lifecycleBeans.entrySet()) { 189 Lifecycle bean = entry.getValue(); 190 int shutdownPhase = getPhase(bean); 191 LifecycleGroup group = phases.get(shutdownPhase); 192 if (group == null) { 193 group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false); 194 phases.put(shutdownPhase, group); 195 } 196 group.add(entry.getKey(), bean); 197 } 198 if (!phases.isEmpty()) { 199 List<Integer> keys = new ArrayList<Integer>(phases.keySet()); 200 Collections.sort(keys, Collections.reverseOrder()); 201 for (Integer key : keys) { 202 phases.get(key).stop(); 203 } 204 } 205 } 206 207 /** 208 * Stop the specified bean as part of the given set of Lifecycle beans, 209 * making sure that any beans that depends on it are stopped first. 210 * @param lifecycleBeans a Map with bean name as key and Lifecycle instance as value 211 * @param beanName the name of the bean to stop 212 */ 213 private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName, 214 final CountDownLatch latch, final Set<String> countDownBeanNames) { 215 216 Lifecycle bean = lifecycleBeans.remove(beanName); 217 if (bean != null) { 218 String[] dependentBeans = this.beanFactory.getDependentBeans(beanName); 219 for (String dependentBean : dependentBeans) { 220 doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames); 221 } 222 try { 223 if (bean.isRunning()) { 224 if (bean instanceof SmartLifecycle) { 225 if (logger.isDebugEnabled()) { 226 logger.debug("Asking bean '" + beanName + "' of type [" + 227 bean.getClass().getName() + "] to stop"); 228 } 229 countDownBeanNames.add(beanName); 230 ((SmartLifecycle) bean).stop(new Runnable() { 231 @Override 232 public void run() { 233 latch.countDown(); 234 countDownBeanNames.remove(beanName); 235 if (logger.isDebugEnabled()) { 236 logger.debug("Bean '" + beanName + "' completed its stop procedure"); 237 } 238 } 239 }); 240 } 241 else { 242 if (logger.isDebugEnabled()) { 243 logger.debug("Stopping bean '" + beanName + "' of type [" + 244 bean.getClass().getName() + "]"); 245 } 246 bean.stop(); 247 if (logger.isDebugEnabled()) { 248 logger.debug("Successfully stopped bean '" + beanName + "'"); 249 } 250 } 251 } 252 else if (bean instanceof SmartLifecycle) { 253 // Don't wait for beans that aren't running... 254 latch.countDown(); 255 } 256 } 257 catch (Throwable ex) { 258 if (logger.isWarnEnabled()) { 259 logger.warn("Failed to stop bean '" + beanName + "'", ex); 260 } 261 } 262 } 263 } 264 265 266 // overridable hooks 267 268 /** 269 * Retrieve all applicable Lifecycle beans: all singletons that have already been created, 270 * as well as all SmartLifecycle beans (even if they are marked as lazy-init). 271 * @return the Map of applicable beans, with bean names as keys and bean instances as values 272 */ 273 protected Map<String, Lifecycle> getLifecycleBeans() { 274 Map<String, Lifecycle> beans = new LinkedHashMap<String, Lifecycle>(); 275 String[] beanNames = this.beanFactory.getBeanNamesForType(Lifecycle.class, false, false); 276 for (String beanName : beanNames) { 277 String beanNameToRegister = BeanFactoryUtils.transformedBeanName(beanName); 278 boolean isFactoryBean = this.beanFactory.isFactoryBean(beanNameToRegister); 279 String beanNameToCheck = (isFactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName); 280 if ((this.beanFactory.containsSingleton(beanNameToRegister) && 281 (!isFactoryBean || Lifecycle.class.isAssignableFrom(this.beanFactory.getType(beanNameToCheck)))) || 282 SmartLifecycle.class.isAssignableFrom(this.beanFactory.getType(beanNameToCheck))) { 283 Lifecycle bean = this.beanFactory.getBean(beanNameToCheck, Lifecycle.class); 284 if (bean != this) { 285 beans.put(beanNameToRegister, bean); 286 } 287 } 288 } 289 return beans; 290 } 291 292 /** 293 * Determine the lifecycle phase of the given bean. 294 * <p>The default implementation checks for the {@link Phased} interface, using 295 * a default of 0 otherwise. Can be overridden to apply other/further policies. 296 * @param bean the bean to introspect 297 * @return the phase (an integer value) 298 * @see Phased#getPhase() 299 * @see SmartLifecycle 300 */ 301 protected int getPhase(Lifecycle bean) { 302 return (bean instanceof Phased ? ((Phased) bean).getPhase() : 0); 303 } 304 305 306 /** 307 * Helper class for maintaining a group of Lifecycle beans that should be started 308 * and stopped together based on their 'phase' value (or the default value of 0). 309 */ 310 private class LifecycleGroup { 311 312 private final int phase; 313 314 private final long timeout; 315 316 private final Map<String, ? extends Lifecycle> lifecycleBeans; 317 318 private final boolean autoStartupOnly; 319 320 private final List<LifecycleGroupMember> members = new ArrayList<LifecycleGroupMember>(); 321 322 private int smartMemberCount; 323 324 public LifecycleGroup( 325 int phase, long timeout, Map<String, ? extends Lifecycle> lifecycleBeans, boolean autoStartupOnly) { 326 327 this.phase = phase; 328 this.timeout = timeout; 329 this.lifecycleBeans = lifecycleBeans; 330 this.autoStartupOnly = autoStartupOnly; 331 } 332 333 public void add(String name, Lifecycle bean) { 334 this.members.add(new LifecycleGroupMember(name, bean)); 335 if (bean instanceof SmartLifecycle) { 336 this.smartMemberCount++; 337 } 338 } 339 340 public void start() { 341 if (this.members.isEmpty()) { 342 return; 343 } 344 if (logger.isInfoEnabled()) { 345 logger.info("Starting beans in phase " + this.phase); 346 } 347 Collections.sort(this.members); 348 for (LifecycleGroupMember member : this.members) { 349 if (this.lifecycleBeans.containsKey(member.name)) { 350 doStart(this.lifecycleBeans, member.name, this.autoStartupOnly); 351 } 352 } 353 } 354 355 public void stop() { 356 if (this.members.isEmpty()) { 357 return; 358 } 359 if (logger.isInfoEnabled()) { 360 logger.info("Stopping beans in phase " + this.phase); 361 } 362 Collections.sort(this.members, Collections.reverseOrder()); 363 CountDownLatch latch = new CountDownLatch(this.smartMemberCount); 364 Set<String> countDownBeanNames = Collections.synchronizedSet(new LinkedHashSet<String>()); 365 for (LifecycleGroupMember member : this.members) { 366 if (this.lifecycleBeans.containsKey(member.name)) { 367 doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames); 368 } 369 else if (member.bean instanceof SmartLifecycle) { 370 // Already removed: must have been a dependent bean from another phase 371 latch.countDown(); 372 } 373 } 374 try { 375 latch.await(this.timeout, TimeUnit.MILLISECONDS); 376 if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isWarnEnabled()) { 377 logger.warn("Failed to shut down " + countDownBeanNames.size() + " bean" + 378 (countDownBeanNames.size() > 1 ? "s" : "") + " with phase value " + 379 this.phase + " within timeout of " + this.timeout + ": " + countDownBeanNames); 380 } 381 } 382 catch (InterruptedException ex) { 383 Thread.currentThread().interrupt(); 384 } 385 } 386 } 387 388 389 /** 390 * Adapts the Comparable interface onto the lifecycle phase model. 391 */ 392 private class LifecycleGroupMember implements Comparable<LifecycleGroupMember> { 393 394 private final String name; 395 396 private final Lifecycle bean; 397 398 LifecycleGroupMember(String name, Lifecycle bean) { 399 this.name = name; 400 this.bean = bean; 401 } 402 403 @Override 404 public int compareTo(LifecycleGroupMember other) { 405 int thisPhase = getPhase(this.bean); 406 int otherPhase = getPhase(other.bean); 407 return (thisPhase == otherPhase ? 0 : (thisPhase < otherPhase) ? -1 : 1); 408 } 409 } 410 411}