/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.beans.factory.annotation; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation * that invokes annotated init and destroy methods. Allows for an annotation * alternative to Spring's {@link org.springframework.beans.factory.InitializingBean} * and {@link org.springframework.beans.factory.DisposableBean} callback interfaces. * * <p>The actual annotation types that this post-processor checks for can be * configured through the {@link #setInitAnnotationType "initAnnotationType"} * and {@link #setDestroyAnnotationType "destroyAnnotationType"} properties. * Any custom annotation can be used, since there are no required annotation * attributes. * * <p>Init and destroy annotations may be applied to methods of any visibility: * public, package-protected, protected, or private. Multiple such methods * may be annotated, but it is recommended to only annotate one single * init method and destroy method, respectively. * * <p>Spring's {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} * supports the JSR-250 {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy} * annotations out of the box, as init annotation and destroy annotation, respectively. * Furthermore, it also supports the {@link javax.annotation.Resource} annotation * for annotation-driven injection of named beans. * * @author Juergen Hoeller * @since 2.5 * @see #setInitAnnotationType * @see #setDestroyAnnotationType */ @SuppressWarnings("serial") public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, PriorityOrdered, Serializable { protected transient Log logger = LogFactory.getLog(getClass()); private Class<? extends Annotation> initAnnotationType; private Class<? extends Annotation> destroyAnnotationType; private int order = Ordered.LOWEST_PRECEDENCE; private transient final Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<>(256); /** * Specify the init annotation to check for, indicating initialization * methods to call after configuration of a bean. * <p>Any custom annotation can be used, since there are no required * annotation attributes. There is no default, although a typical choice * is the JSR-250 {@link javax.annotation.PostConstruct} annotation. */ public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) { this.initAnnotationType = initAnnotationType; } /** * Specify the destroy annotation to check for, indicating destruction * methods to call when the context is shutting down. * <p>Any custom annotation can be used, since there are no required * annotation attributes. There is no default, although a typical choice * is the JSR-250 {@link javax.annotation.PreDestroy} annotation. */ public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) { this.destroyAnnotationType = destroyAnnotationType; } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { if (beanType != null) { LifecycleMetadata metadata = findLifecycleMetadata(beanType); metadata.checkConfigMembers(beanDefinition); } } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeDestroyMethods(bean, beanName); } catch (InvocationTargetException ex) { String msg = "Invocation of destroy method failed on bean with name '" + beanName + "'"; if (logger.isDebugEnabled()) { logger.warn(msg, ex.getTargetException()); } else { logger.warn(msg + ": " + ex.getTargetException()); } } catch (Throwable ex) { logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex); } } @Override public boolean requiresDestruction(Object bean) { return findLifecycleMetadata(bean.getClass()).hasDestroyMethods(); } private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { if (this.lifecycleMetadataCache == null) { // Happens after deserialization, during destruction... return buildLifecycleMetadata(clazz); } // Quick check on the concurrent map first, with minimal locking. LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { synchronized (this.lifecycleMetadataCache) { metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { metadata = buildLifecycleMetadata(clazz); this.lifecycleMetadataCache.put(clazz, metadata); } return metadata; } } return metadata; } private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { final boolean debug = logger.isDebugEnabled(); LinkedList<LifecycleElement> initMethods = new LinkedList<>(); LinkedList<LifecycleElement> destroyMethods = new LinkedList<>(); Class<?> targetClass = clazz; do { final LinkedList<LifecycleElement> currInitMethods = new LinkedList<>(); final LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<>(); ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { if (initAnnotationType != null) { if (method.getAnnotation(initAnnotationType) != null) { LifecycleElement element = new LifecycleElement(method); currInitMethods.add(element); if (debug) { logger.debug("Found init method on class [" + clazz.getName() + "]: " + method); } } } if (destroyAnnotationType != null) { if (method.getAnnotation(destroyAnnotationType) != null) { currDestroyMethods.add(new LifecycleElement(method)); if (debug) { logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method); } } } } }); initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new LifecycleMetadata(clazz, initMethods, destroyMethods); } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Initialize transient fields. this.logger = LogFactory.getLog(getClass()); } /** * Class representing information about annotated init and destroy methods. */ private class LifecycleMetadata { private final Class<?> targetClass; private final Collection<LifecycleElement> initMethods; private final Collection<LifecycleElement> destroyMethods; private volatile Set<LifecycleElement> checkedInitMethods; private volatile Set<LifecycleElement> checkedDestroyMethods; public LifecycleMetadata(Class<?> targetClass, Collection<LifecycleElement> initMethods, Collection<LifecycleElement> destroyMethods) { this.targetClass = targetClass; this.initMethods = initMethods; this.destroyMethods = destroyMethods; } public void checkConfigMembers(RootBeanDefinition beanDefinition) { Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size()); for (LifecycleElement element : this.initMethods) { String methodIdentifier = element.getIdentifier(); if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedInitMethod(methodIdentifier); checkedInitMethods.add(element); if (logger.isDebugEnabled()) { logger.debug("Registered init method on class [" + this.targetClass.getName() + "]: " + element); } } } Set<LifecycleElement> checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size()); for (LifecycleElement element : this.destroyMethods) { String methodIdentifier = element.getIdentifier(); if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) { beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier); checkedDestroyMethods.add(element); if (logger.isDebugEnabled()) { logger.debug("Registered destroy method on class [" + this.targetClass.getName() + "]: " + element); } } } this.checkedInitMethods = checkedInitMethods; this.checkedDestroyMethods = checkedDestroyMethods; } public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection<LifecycleElement> initMethodsToIterate = (this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (LifecycleElement element : initMethodsToIterate) { if (debug) { logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } element.invoke(target); } } } public void invokeDestroyMethods(Object target, String beanName) throws Throwable { Collection<LifecycleElement> destroyMethodsToUse = (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods); if (!destroyMethodsToUse.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (LifecycleElement element : destroyMethodsToUse) { if (debug) { logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod()); } element.invoke(target); } } } public boolean hasDestroyMethods() { Collection<LifecycleElement> destroyMethodsToUse = (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods); return !destroyMethodsToUse.isEmpty(); } } /** * Class representing injection information about an annotated method. */ private static class LifecycleElement { private final Method method; private final String identifier; public LifecycleElement(Method method) { if (method.getParameterCount() != 0) { throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method); } this.method = method; this.identifier = (Modifier.isPrivate(method.getModifiers()) ? ClassUtils.getQualifiedMethodName(method) : method.getName()); } public Method getMethod() { return this.method; } public String getIdentifier() { return this.identifier; } public void invoke(Object target) throws Throwable { ReflectionUtils.makeAccessible(this.method); this.method.invoke(target, (Object[]) null); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof LifecycleElement)) { return false; } LifecycleElement otherElement = (LifecycleElement) other; return (this.identifier.equals(otherElement.identifier)); } @Override public int hashCode() { return this.identifier.hashCode(); } } }