Open Source Repository

Home /spring/spring-beans-3.0.5 | Repository Home



org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java
/*
 * 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 beanNamethrows 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 beanNamethrows BeansException {
    return bean;
  }

  public void postProcessBeforeDestruction(Object bean, String beanNamethrows 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 oisthrows 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 beanNamethrows 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 beanNamethrows 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 targetthrows 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 = (LifecycleElementother;
      return (this.identifier.equals(otherElement.identifier));
    }

    @Override
    public int hashCode() {
      return this.identifier.hashCode();
    }
  }

}