001/*
002 * Copyright 2002-2015 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.beans.factory.annotation;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.lang.reflect.Modifier;
026import java.util.Collection;
027import java.util.LinkedHashSet;
028import java.util.LinkedList;
029import java.util.Map;
030import java.util.Set;
031import java.util.concurrent.ConcurrentHashMap;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.beans.BeansException;
037import org.springframework.beans.factory.BeanCreationException;
038import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
039import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
040import org.springframework.beans.factory.support.RootBeanDefinition;
041import org.springframework.core.Ordered;
042import org.springframework.core.PriorityOrdered;
043import org.springframework.util.ClassUtils;
044import org.springframework.util.ReflectionUtils;
045
046/**
047 * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
048 * that invokes annotated init and destroy methods. Allows for an annotation
049 * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean}
050 * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces.
051 *
052 * <p>The actual annotation types that this post-processor checks for can be
053 * configured through the {@link #setInitAnnotationType "initAnnotationType"}
054 * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties.
055 * Any custom annotation can be used, since there are no required annotation
056 * attributes.
057 *
058 * <p>Init and destroy annotations may be applied to methods of any visibility:
059 * public, package-protected, protected, or private. Multiple such methods
060 * may be annotated, but it is recommended to only annotate one single
061 * init method and destroy method, respectively.
062 *
063 * <p>Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}
064 * supports the JSR-250 {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy}
065 * annotations out of the box, as init annotation and destroy annotation, respectively.
066 * Furthermore, it also supports the {@link javax.annotation.Resource} annotation
067 * for annotation-driven injection of named beans.
068 *
069 * @author Juergen Hoeller
070 * @since 2.5
071 * @see #setInitAnnotationType
072 * @see #setDestroyAnnotationType
073 */
074@SuppressWarnings("serial")
075public class InitDestroyAnnotationBeanPostProcessor
076                implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable {
077
078        protected transient Log logger = LogFactory.getLog(getClass());
079
080        private Class<? extends Annotation> initAnnotationType;
081
082        private Class<? extends Annotation> destroyAnnotationType;
083
084        private int order = Ordered.LOWEST_PRECEDENCE;
085
086        private transient final Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache =
087                        new ConcurrentHashMap<Class<?>, LifecycleMetadata>(256);
088
089
090        /**
091         * Specify the init annotation to check for, indicating initialization
092         * methods to call after configuration of a bean.
093         * <p>Any custom annotation can be used, since there are no required
094         * annotation attributes. There is no default, although a typical choice
095         * is the JSR-250 {@link javax.annotation.PostConstruct} annotation.
096         */
097        public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
098                this.initAnnotationType = initAnnotationType;
099        }
100
101        /**
102         * Specify the destroy annotation to check for, indicating destruction
103         * methods to call when the context is shutting down.
104         * <p>Any custom annotation can be used, since there are no required
105         * annotation attributes. There is no default, although a typical choice
106         * is the JSR-250 {@link javax.annotation.PreDestroy} annotation.
107         */
108        public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
109                this.destroyAnnotationType = destroyAnnotationType;
110        }
111
112        public void setOrder(int order) {
113                this.order = order;
114        }
115
116        @Override
117        public int getOrder() {
118                return this.order;
119        }
120
121
122        @Override
123        public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
124                if (beanType != null) {
125                        LifecycleMetadata metadata = findLifecycleMetadata(beanType);
126                        metadata.checkConfigMembers(beanDefinition);
127                }
128        }
129
130        @Override
131        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
132                LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
133                try {
134                        metadata.invokeInitMethods(bean, beanName);
135                }
136                catch (InvocationTargetException ex) {
137                        throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
138                }
139                catch (Throwable ex) {
140                        throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
141                }
142                return bean;
143        }
144
145        @Override
146        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
147                return bean;
148        }
149
150        @Override
151        public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
152                LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
153                try {
154                        metadata.invokeDestroyMethods(bean, beanName);
155                }
156                catch (InvocationTargetException ex) {
157                        String msg = "Invocation of destroy method failed on bean with name '" + beanName + "'";
158                        if (logger.isDebugEnabled()) {
159                                logger.warn(msg, ex.getTargetException());
160                        }
161                        else {
162                                logger.warn(msg + ": " + ex.getTargetException());
163                        }
164                }
165                catch (Throwable ex) {
166                        logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
167                }
168        }
169
170        @Override
171        public boolean requiresDestruction(Object bean) {
172                return findLifecycleMetadata(bean.getClass()).hasDestroyMethods();
173        }
174
175
176        private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
177                if (this.lifecycleMetadataCache == null) {
178                        // Happens after deserialization, during destruction...
179                        return buildLifecycleMetadata(clazz);
180                }
181                // Quick check on the concurrent map first, with minimal locking.
182                LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
183                if (metadata == null) {
184                        synchronized (this.lifecycleMetadataCache) {
185                                metadata = this.lifecycleMetadataCache.get(clazz);
186                                if (metadata == null) {
187                                        metadata = buildLifecycleMetadata(clazz);
188                                        this.lifecycleMetadataCache.put(clazz, metadata);
189                                }
190                                return metadata;
191                        }
192                }
193                return metadata;
194        }
195
196        private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
197                final boolean debug = logger.isDebugEnabled();
198                LinkedList<LifecycleElement> initMethods = new LinkedList<LifecycleElement>();
199                LinkedList<LifecycleElement> destroyMethods = new LinkedList<LifecycleElement>();
200                Class<?> targetClass = clazz;
201
202                do {
203                        final LinkedList<LifecycleElement> currInitMethods = new LinkedList<LifecycleElement>();
204                        final LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<LifecycleElement>();
205
206                        ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
207                                @Override
208                                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
209                                        if (initAnnotationType != null) {
210                                                if (method.getAnnotation(initAnnotationType) != null) {
211                                                        LifecycleElement element = new LifecycleElement(method);
212                                                        currInitMethods.add(element);
213                                                        if (debug) {
214                                                                logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
215                                                        }
216                                                }
217                                        }
218                                        if (destroyAnnotationType != null) {
219                                                if (method.getAnnotation(destroyAnnotationType) != null) {
220                                                        currDestroyMethods.add(new LifecycleElement(method));
221                                                        if (debug) {
222                                                                logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
223                                                        }
224                                                }
225                                        }
226                                }
227                        });
228
229                        initMethods.addAll(0, currInitMethods);
230                        destroyMethods.addAll(currDestroyMethods);
231                        targetClass = targetClass.getSuperclass();
232                }
233                while (targetClass != null && targetClass != Object.class);
234
235                return new LifecycleMetadata(clazz, initMethods, destroyMethods);
236        }
237
238
239        //---------------------------------------------------------------------
240        // Serialization support
241        //---------------------------------------------------------------------
242
243        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
244                // Rely on default serialization; just initialize state after deserialization.
245                ois.defaultReadObject();
246
247                // Initialize transient fields.
248                this.logger = LogFactory.getLog(getClass());
249        }
250
251
252        /**
253         * Class representing information about annotated init and destroy methods.
254         */
255        private class LifecycleMetadata {
256
257                private final Class<?> targetClass;
258
259                private final Collection<LifecycleElement> initMethods;
260
261                private final Collection<LifecycleElement> destroyMethods;
262
263                private volatile Set<LifecycleElement> checkedInitMethods;
264
265                private volatile Set<LifecycleElement> checkedDestroyMethods;
266
267                public LifecycleMetadata(Class<?> targetClass, Collection<LifecycleElement> initMethods,
268                                Collection<LifecycleElement> destroyMethods) {
269
270                        this.targetClass = targetClass;
271                        this.initMethods = initMethods;
272                        this.destroyMethods = destroyMethods;
273                }
274
275                public void checkConfigMembers(RootBeanDefinition beanDefinition) {
276                        Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<LifecycleElement>(this.initMethods.size());
277                        for (LifecycleElement element : this.initMethods) {
278                                String methodIdentifier = element.getIdentifier();
279                                if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) {
280                                        beanDefinition.registerExternallyManagedInitMethod(methodIdentifier);
281                                        checkedInitMethods.add(element);
282                                        if (logger.isDebugEnabled()) {
283                                                logger.debug("Registered init method on class [" + this.targetClass.getName() + "]: " + element);
284                                        }
285                                }
286                        }
287                        Set<LifecycleElement> checkedDestroyMethods = new LinkedHashSet<LifecycleElement>(this.destroyMethods.size());
288                        for (LifecycleElement element : this.destroyMethods) {
289                                String methodIdentifier = element.getIdentifier();
290                                if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) {
291                                        beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier);
292                                        checkedDestroyMethods.add(element);
293                                        if (logger.isDebugEnabled()) {
294                                                logger.debug("Registered destroy method on class [" + this.targetClass.getName() + "]: " + element);
295                                        }
296                                }
297                        }
298                        this.checkedInitMethods = checkedInitMethods;
299                        this.checkedDestroyMethods = checkedDestroyMethods;
300                }
301
302                public void invokeInitMethods(Object target, String beanName) throws Throwable {
303                        Collection<LifecycleElement> initMethodsToIterate =
304                                        (this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods);
305                        if (!initMethodsToIterate.isEmpty()) {
306                                boolean debug = logger.isDebugEnabled();
307                                for (LifecycleElement element : initMethodsToIterate) {
308                                        if (debug) {
309                                                logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod());
310                                        }
311                                        element.invoke(target);
312                                }
313                        }
314                }
315
316                public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
317                        Collection<LifecycleElement> destroyMethodsToUse =
318                                        (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
319                        if (!destroyMethodsToUse.isEmpty()) {
320                                boolean debug = logger.isDebugEnabled();
321                                for (LifecycleElement element : destroyMethodsToUse) {
322                                        if (debug) {
323                                                logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
324                                        }
325                                        element.invoke(target);
326                                }
327                        }
328                }
329
330                public boolean hasDestroyMethods() {
331                        Collection<LifecycleElement> destroyMethodsToUse =
332                                        (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
333                        return !destroyMethodsToUse.isEmpty();
334                }
335        }
336
337
338        /**
339         * Class representing injection information about an annotated method.
340         */
341        private static class LifecycleElement {
342
343                private final Method method;
344
345                private final String identifier;
346
347                public LifecycleElement(Method method) {
348                        if (method.getParameterTypes().length != 0) {
349                                throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method);
350                        }
351                        this.method = method;
352                        this.identifier = (Modifier.isPrivate(method.getModifiers()) ?
353                                        ClassUtils.getQualifiedMethodName(method) : method.getName());
354                }
355
356                public Method getMethod() {
357                        return this.method;
358                }
359
360                public String getIdentifier() {
361                        return this.identifier;
362                }
363
364                public void invoke(Object target) throws Throwable {
365                        ReflectionUtils.makeAccessible(this.method);
366                        this.method.invoke(target, (Object[]) null);
367                }
368
369                @Override
370                public boolean equals(Object other) {
371                        if (this == other) {
372                                return true;
373                        }
374                        if (!(other instanceof LifecycleElement)) {
375                                return false;
376                        }
377                        LifecycleElement otherElement = (LifecycleElement) other;
378                        return (this.identifier.equals(otherElement.identifier));
379                }
380
381                @Override
382                public int hashCode() {
383                        return this.identifier.hashCode();
384                }
385        }
386
387}