Open Source Repository

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



org/springframework/beans/factory/support/DisposableBeanAdapter.java
/*
 * Copyright 2002-2010 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.support;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
 * Adapter that implements the {@link DisposableBean} and {@link Runnable} interfaces
 * performing various destruction steps on a given bean instance:
 <ul>
 <li>DestructionAwareBeanPostProcessors;
 <li>the bean implementing DisposableBean itself;
 <li>a custom destroy method specified on the bean definition.
 </ul>
 *
 @author Juergen Hoeller
 @author Costin Leau
 @since 2.0
 @see AbstractBeanFactory
 @see org.springframework.beans.factory.DisposableBean
 @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
 @see AbstractBeanDefinition#getDestroyMethodName()
 */
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {

  private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);

  private final Object bean;

  private final String beanName;

  private final boolean invokeDisposableBean;

  private final boolean nonPublicAccessAllowed;

  private String destroyMethodName;

  private transient Method destroyMethod;

  private List<DestructionAwareBeanPostProcessor> beanPostProcessors;

  private final AccessControlContext acc;


  /**
   * Create a new DisposableBeanAdapter for the given bean.
   @param bean the bean instance (never <code>null</code>)
   @param beanName the name of the bean
   @param beanDefinition the merged bean definition
   @param postProcessors the List of BeanPostProcessors
   * (potentially DestructionAwareBeanPostProcessor), if any
   */
  public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition,
      List<BeanPostProcessor> postProcessors, AccessControlContext acc) {

    Assert.notNull(bean, "Disposable bean must not be null");
    this.bean = bean;
    this.beanName = beanName;
    this.invokeDisposableBean =
        (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
    this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
    this.acc = acc;
    
    final String destroyMethodName = beanDefinition.getDestroyMethodName();
    if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
        !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
      this.destroyMethodName = destroyMethodName;
      this.destroyMethod = determineDestroyMethod();
      if (this.destroyMethod == null) {
        if (beanDefinition.isEnforceDestroyMethod()) {
          throw new BeanDefinitionValidationException("Couldn't find a destroy method named '" +
              destroyMethodName + "' on bean with name '" + beanName + "'");
        }
      }
      else {
        Class[] paramTypes = this.destroyMethod.getParameterTypes();
        if (paramTypes.length > 1) {
          throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
              beanName + "' has more than one parameter - not supported as destroy method");
        }
        else if (paramTypes.length == && !paramTypes[0].equals(boolean.class)) {
          throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
              beanName + "' has a non-boolean parameter - not supported as destroy method");
        }
      }
    }
    this.beanPostProcessors = filterPostProcessors(postProcessors);
  }

  /**
   * Create a new DisposableBeanAdapter for the given bean.
   */
  private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean,
      boolean nonPublicAccessAllowed, String destroyMethodName,
      List<DestructionAwareBeanPostProcessor> postProcessors) {

    this.bean = bean;
    this.beanName = beanName;
    this.invokeDisposableBean = invokeDisposableBean;
    this.nonPublicAccessAllowed = nonPublicAccessAllowed;
    this.destroyMethodName = destroyMethodName;
    this.beanPostProcessors = postProcessors;
    this.acc = null;
  }


  /**
   * Search for all DestructionAwareBeanPostProcessors in the List.
   @param postProcessors the List to search
   @return the filtered List of DestructionAwareBeanPostProcessors
   */
  private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> postProcessors) {
    List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
    if (postProcessors != null && !postProcessors.isEmpty()) {
      filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(postProcessors.size());
      for (BeanPostProcessor postProcessor : postProcessors) {
        if (postProcessor instanceof DestructionAwareBeanPostProcessor) {
          filteredPostProcessors.add((DestructionAwareBeanPostProcessorpostProcessor);
        }
      }
    }
    return filteredPostProcessors;
  }


  public void run() {
    destroy();
  }

  public void destroy() {
    if (this.beanPostProcessors != null && !this.beanPostProcessors.isEmpty()) {
      for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
        processor.postProcessBeforeDestruction(this.bean, this.beanName);
      }
    }

    if (this.invokeDisposableBean) {
      if (logger.isDebugEnabled()) {
        logger.debug("Invoking destroy() on bean with name '" this.beanName + "'");
      }
      try {
        if (System.getSecurityManager() != null) {
          AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
            public Object run() throws Exception {
              ((DisposableBeanbean).destroy();
              return null;
            }
          }, acc);
        }
        else {
          ((DisposableBeanbean).destroy();
        }
      }
      catch (Throwable ex) {
        String msg = "Invocation of destroy method failed on bean with name '" this.beanName + "'";
        if (logger.isDebugEnabled()) {
          logger.warn(msg, ex);
        }
        else {
          logger.warn(msg + ": " + ex);
        }
      }
    }

    if (this.destroyMethod != null) {
      invokeCustomDestroyMethod(this.destroyMethod);
    }
    else if (this.destroyMethodName != null) {
      Method methodToCall = determineDestroyMethod();
      if (methodToCall != null) {
        invokeCustomDestroyMethod(methodToCall);
      }
    }
  }


  private Method determineDestroyMethod() {
    try {
      if (System.getSecurityManager() != null) {
        return AccessController.doPrivileged(new PrivilegedAction<Method>() {
          public Method run() {
            return findDestroyMethod();
          }
        });
      }
      else {
        return findDestroyMethod();
      }
    }
    catch (IllegalArgumentException ex) {
      throw new BeanDefinitionValidationException("Couldn't find a unique destroy method on bean with name '" +
          this.beanName + ": " + ex.getMessage());
    }
  }

  private Method findDestroyMethod() {
    return (this.nonPublicAccessAllowed ?
        BeanUtils.findMethodWithMinimalParameters(this.bean.getClass()this.destroyMethodName:
        BeanUtils.findMethodWithMinimalParameters(this.bean.getClass().getMethods()this.destroyMethodName));
  }

  /**
   * Invoke the specified custom destroy method on the given bean.
   <p>This implementation invokes a no-arg method if found, else checking
   * for a method with a single boolean argument (passing in "true",
   * assuming a "force" parameter), else logging an error.
   */
  private void invokeCustomDestroyMethod(final Method destroyMethod) {
    Class[] paramTypes = destroyMethod.getParameterTypes();
    final Object[] args = new Object[paramTypes.length];
    if (paramTypes.length == 1) {
      args[0= Boolean.TRUE;
    }
    if (logger.isDebugEnabled()) {
      logger.debug("Invoking destroy method '" this.destroyMethodName +
          "' on bean with name '" this.beanName + "'");
    }
    try {
      if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
          public Object run() {
            ReflectionUtils.makeAccessible(destroyMethod);
            return null;
          }
        });
        try {
          AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
            public Object run() throws Exception {
              destroyMethod.invoke(bean, args);
              return null;
            }
          }, acc);
        }
        catch (PrivilegedActionException pax) {
          throw (InvocationTargetExceptionpax.getException();
        }
      }
      else {
        ReflectionUtils.makeAccessible(destroyMethod);
        destroyMethod.invoke(bean, args);
      }
    }
    catch (InvocationTargetException ex) {
      String msg = "Invocation of destroy method '" this.destroyMethodName +
          "' failed on bean with name '" this.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 '" this.destroyMethodName +
          "' on bean with name '" this.beanName + "'", ex);
    }
  }


  /**
   * Serializes a copy of the state of this class,
   * filtering out non-serializable BeanPostProcessors.
   */
  protected Object writeReplace() {
    List<DestructionAwareBeanPostProcessor> serializablePostProcessors = null;
    if (this.beanPostProcessors != null) {
      serializablePostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>();
      for (DestructionAwareBeanPostProcessor postProcessor : this.beanPostProcessors) {
        if (postProcessor instanceof Serializable) {
          serializablePostProcessors.add(postProcessor);
        }
      }
    }
    return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean,
        this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
  }

}