All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.beans.factory.support.DisposableBeanAdapter Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 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.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * Adapter that implements the {@link DisposableBean} and {@link Runnable}
 * interfaces performing various destruction steps on a given bean instance:
 * 
    *
  • DestructionAwareBeanPostProcessors; *
  • the bean implementing DisposableBean itself; *
  • a custom destroy method specified on the bean definition. *
* * @author Juergen Hoeller * @author Costin Leau * @author Stephane Nicoll * @since 2.0 * @see AbstractBeanFactory * @see org.springframework.beans.factory.DisposableBean * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor * @see AbstractBeanDefinition#getDestroyMethodName() */ @SuppressWarnings("serial") class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private static final String CLOSE_METHOD_NAME = "close"; private static final String SHUTDOWN_METHOD_NAME = "shutdown"; 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; @Nullable private final AccessControlContext acc; @Nullable private String destroyMethodName; @Nullable private transient Method destroyMethod; @Nullable private List beanPostProcessors; /** * Create a new DisposableBeanAdapter for the given bean. * @param bean the bean instance (never {@code null}) * @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 postProcessors, @Nullable 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; String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) && !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) { this.destroyMethodName = destroyMethodName; this.destroyMethod = determineDestroyMethod(destroyMethodName); if (this.destroyMethod == null) { if (beanDefinition.isEnforceDestroyMethod()) { throw new BeanDefinitionValidationException("Could not 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 == 1 && boolean.class != paramTypes[0]) { throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" + beanName + "' has a non-boolean parameter - not supported as destroy method"); } } } this.beanPostProcessors = filterPostProcessors(postProcessors, bean); } /** * Create a new DisposableBeanAdapter for the given bean. * @param bean the bean instance (never {@code null}) * @param postProcessors the List of BeanPostProcessors * (potentially DestructionAwareBeanPostProcessor), if any */ public DisposableBeanAdapter(Object bean, List postProcessors, AccessControlContext acc) { Assert.notNull(bean, "Disposable bean must not be null"); this.bean = bean; this.beanName = bean.getClass().getName(); this.invokeDisposableBean = (this.bean instanceof DisposableBean); this.nonPublicAccessAllowed = true; this.acc = acc; this.beanPostProcessors = filterPostProcessors(postProcessors, bean); } /** * Create a new DisposableBeanAdapter for the given bean. */ private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean, boolean nonPublicAccessAllowed, @Nullable String destroyMethodName, @Nullable List postProcessors) { this.bean = bean; this.beanName = beanName; this.invokeDisposableBean = invokeDisposableBean; this.nonPublicAccessAllowed = nonPublicAccessAllowed; this.acc = null; this.destroyMethodName = destroyMethodName; this.beanPostProcessors = postProcessors; } /** * If the current value of the given beanDefinition's "destroyMethodName" property is * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method. * Candidate methods are currently limited to public, no-arg methods named "close" or * "shutdown" (whether declared locally or inherited). The given BeanDefinition's * "destroyMethodName" is updated to be null if no such method is found, otherwise set * to the name of the inferred method. This constant serves as the default for the * {@code @Bean#destroyMethod} attribute and the value of the constant may also be * used in XML within the {@code } or {@code * } attributes. *

Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} * interfaces, reflectively calling the "close" method on implementing beans as well. */ @Nullable private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { String destroyMethodName = beanDefinition.getDestroyMethodName(); if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || (destroyMethodName == null && bean instanceof AutoCloseable)) { // Only perform destroy method inference or Closeable detection // in case of the bean not explicitly implementing DisposableBean if (!(bean instanceof DisposableBean)) { try { return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); } catch (NoSuchMethodException ex) { try { return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); } catch (NoSuchMethodException ex2) { // no candidate destroy method found } } } return null; } return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null); } /** * Search for all DestructionAwareBeanPostProcessors in the List. * @param processors the List to search * @return the filtered List of DestructionAwareBeanPostProcessors */ @Nullable private List filterPostProcessors(List processors, Object bean) { List filteredPostProcessors = null; if (!CollectionUtils.isEmpty(processors)) { filteredPostProcessors = new ArrayList<>(processors.size()); for (BeanPostProcessor processor : processors) { if (processor instanceof DestructionAwareBeanPostProcessor) { DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; if (dabpp.requiresDestruction(bean)) { filteredPostProcessors.add(dabpp); } } } } return filteredPostProcessors; } @Override public void run() { destroy(); } @Override public void destroy() { if (!CollectionUtils.isEmpty(this.beanPostProcessors)) { for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) { processor.postProcessBeforeDestruction(this.bean, this.beanName); } } if (this.invokeDisposableBean) { if (logger.isTraceEnabled()) { logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'"); } try { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedExceptionAction) () -> { ((DisposableBean) this.bean).destroy(); return null; }, this.acc); } else { ((DisposableBean) this.bean).destroy(); } } catch (Throwable ex) { String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'"; if (logger.isDebugEnabled()) { logger.info(msg, ex); } else { logger.info(msg + ": " + ex); } } } if (this.destroyMethod != null) { invokeCustomDestroyMethod(this.destroyMethod); } else if (this.destroyMethodName != null) { Method methodToCall = determineDestroyMethod(this.destroyMethodName); if (methodToCall != null) { invokeCustomDestroyMethod(methodToCall); } } } @Nullable private Method determineDestroyMethod(String name) { try { if (System.getSecurityManager() != null) { return AccessController.doPrivileged((PrivilegedAction) () -> findDestroyMethod(name)); } else { return findDestroyMethod(name); } } catch (IllegalArgumentException ex) { throw new BeanDefinitionValidationException("Could not find unique destroy method on bean with name '" + this.beanName + ": " + ex.getMessage()); } } @Nullable private Method findDestroyMethod(String name) { return (this.nonPublicAccessAllowed ? BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), name) : BeanUtils.findMethodWithMinimalParameters(this.bean.getClass().getMethods(), name)); } /** * Invoke the specified custom destroy method on the given bean. *

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.isTraceEnabled()) { logger.trace("Invoking destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "'"); } try { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction) () -> { ReflectionUtils.makeAccessible(destroyMethod); return null; }); try { AccessController.doPrivileged((PrivilegedExceptionAction) () -> destroyMethod.invoke(this.bean, args), this.acc); } catch (PrivilegedActionException pax) { throw (InvocationTargetException) pax.getException(); } } else { ReflectionUtils.makeAccessible(destroyMethod); destroyMethod.invoke(this.bean, args); } } catch (InvocationTargetException ex) { String msg = "Destroy method '" + this.destroyMethodName + "' on bean with name '" + this.beanName + "' threw an exception"; if (logger.isDebugEnabled()) { logger.info(msg, ex.getTargetException()); } else { logger.info(msg + ": " + ex.getTargetException()); } } catch (Throwable ex) { logger.info("Failed to 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 serializablePostProcessors = null; if (this.beanPostProcessors != null) { serializablePostProcessors = new ArrayList<>(); 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); } /** * Check whether the given bean has any kind of destroy method to call. * @param bean the bean instance * @param beanDefinition the corresponding bean definition */ public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) { if (bean instanceof DisposableBean || bean instanceof AutoCloseable) { return true; } String destroyMethodName = beanDefinition.getDestroyMethodName(); if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); } return StringUtils.hasLength(destroyMethodName); } /** * Check whether the given bean has destruction-aware post-processors applying to it. * @param bean the bean instance * @param postProcessors the post-processor candidates */ public static boolean hasApplicableProcessors(Object bean, List postProcessors) { if (!CollectionUtils.isEmpty(postProcessors)) { for (BeanPostProcessor processor : postProcessors) { if (processor instanceof DestructionAwareBeanPostProcessor) { DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; if (dabpp.requiresDestruction(bean)) { return true; } } } } return false; } }