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}