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