/* * 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.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.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: * <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 * @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; private final AccessControlContext acc; private String destroyMethodName; private transient Method destroyMethod; private List<DestructionAwareBeanPostProcessor> 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<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; String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition); 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 == 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<BeanPostProcessor> postProcessors, AccessControlContext acc) { Assert.notNull(bean, "Disposable bean must not be null"); this.bean = bean; this.beanName = null; 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, String destroyMethodName, List<DestructionAwareBeanPostProcessor> 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 <bean destroy-method="">} or {@code * <beans default-destroy-method="">} attributes. * <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable} * interfaces, reflectively calling the "close" method on implementing beans as well. */ 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 */ private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) { List<DestructionAwareBeanPostProcessor> 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.isDebugEnabled()) { logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'"); } try { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { ((DisposableBean) bean).destroy(); return null; } }, acc); } else { ((DisposableBean) bean).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>() { @Override public Method run() { return findDestroyMethod(); } }); } else { return findDestroyMethod(); } } catch (IllegalArgumentException ex) { throw new BeanDefinitionValidationException("Could not find 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>() { @Override public Object run() { ReflectionUtils.makeAccessible(destroyMethod); return null; } }); try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { destroyMethod.invoke(bean, args); return null; } }, acc); } catch (PrivilegedActionException pax) { throw (InvocationTargetException) pax.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<>(); 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<BeanPostProcessor> postProcessors) { if (!CollectionUtils.isEmpty(postProcessors)) { for (BeanPostProcessor processor : postProcessors) { if (processor instanceof DestructionAwareBeanPostProcessor) { DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; try { if (dabpp.requiresDestruction(bean)) { return true; } } catch (AbstractMethodError err) { // A pre-4.3 third-party DestructionAwareBeanPostProcessor... // As of 5.0, we can let requiresDestruction be a Java 8 default method which returns true. return true; } } } } return false; } }