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}