/*
* Copyright 2002-2009 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.Iterator;
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.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
*/
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<Class<?>, LifecycleMetadata>();
/**
* 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;
}
public int getOrder() {
return this.order;
}
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) {
if (beanType != null) {
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
metadata.checkConfigMembers(beanDefinition);
}
}
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, "Couldn't invoke init method", ex);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
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("Couldn't invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
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(Class clazz) {
final boolean debug = logger.isDebugEnabled();
LinkedList<LifecycleElement> initMethods = new LinkedList<LifecycleElement>();
LinkedList<LifecycleElement> destroyMethods = new LinkedList<LifecycleElement>();
Class<?> targetClass = clazz;
do {
LinkedList<LifecycleElement> currInitMethods = new LinkedList<LifecycleElement>();
LinkedList<LifecycleElement> currDestroyMethods = new LinkedList<LifecycleElement>();
for (Method method : targetClass.getDeclaredMethods()) {
if (this.initAnnotationType != null) {
if (method.getAnnotation(this.initAnnotationType) != null) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (debug) {
logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
}
if (this.destroyAnnotationType != null) {
if (method.getAnnotation(this.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 Set<LifecycleElement> initMethods;
private final Set<LifecycleElement> destroyMethods;
public LifecycleMetadata(Class targetClass, Collection<LifecycleElement> initMethods,
Collection<LifecycleElement> destroyMethods) {
this.initMethods = new LinkedHashSet<LifecycleElement>();
for (LifecycleElement element : initMethods) {
if (logger.isDebugEnabled()) {
logger.debug("Found init method on class [" + targetClass.getName() + "]: " + element);
}
this.initMethods.add(element);
}
this.destroyMethods = new LinkedHashSet<LifecycleElement>();
for (LifecycleElement element : destroyMethods) {
if (logger.isDebugEnabled()) {
logger.debug("Found destroy method on class [" + targetClass.getName() + "]: " + element);
}
this.destroyMethods.add(element);
}
}
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
for (Iterator<LifecycleElement> it = this.initMethods.iterator(); it.hasNext();) {
String methodIdentifier = it.next().getIdentifier();
if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) {
beanDefinition.registerExternallyManagedInitMethod(methodIdentifier);
}
else {
it.remove();
}
}
for (Iterator<LifecycleElement> it = this.destroyMethods.iterator(); it.hasNext();) {
String methodIdentifier = it.next().getIdentifier();
if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) {
beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier);
}
else {
it.remove();
}
}
}
public void invokeInitMethods(Object target, String beanName) throws Throwable {
if (!this.initMethods.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (LifecycleElement element : this.initMethods) {
if (debug) {
logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod());
}
element.invoke(target);
}
}
}
public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
if (!this.destroyMethods.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (LifecycleElement element : this.destroyMethods) {
if (debug) {
logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
}
element.invoke(target);
}
}
}
}
/**
* 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.getParameterTypes().length != 0) {
throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method);
}
this.method = method;
this.identifier = (Modifier.isPrivate(method.getModifiers()) ?
method.getDeclaringClass() + "." + method.getName() : 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();
}
}
}
|