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