View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.beans.factory.support;
18  
19  import java.io.Closeable;
20  import java.io.Serializable;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.security.AccessControlContext;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.security.PrivilegedActionException;
27  import java.security.PrivilegedExceptionAction;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import org.springframework.beans.BeanUtils;
35  import org.springframework.beans.factory.DisposableBean;
36  import org.springframework.beans.factory.config.BeanPostProcessor;
37  import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
38  import org.springframework.util.Assert;
39  import org.springframework.util.ClassUtils;
40  import org.springframework.util.ReflectionUtils;
41  
42  /**
43   * Adapter that implements the {@link DisposableBean} and {@link Runnable} interfaces
44   * performing various destruction steps on a given bean instance:
45   * <ul>
46   * <li>DestructionAwareBeanPostProcessors;
47   * <li>the bean implementing DisposableBean itself;
48   * <li>a custom destroy method specified on the bean definition.
49   * </ul>
50   *
51   * @author Juergen Hoeller
52   * @author Costin Leau
53   * @since 2.0
54   * @see AbstractBeanFactory
55   * @see org.springframework.beans.factory.DisposableBean
56   * @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
57   * @see AbstractBeanDefinition#getDestroyMethodName()
58   */
59  @SuppressWarnings("serial")
60  class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
61  
62  	private static final String CLOSE_METHOD_NAME = "close";
63  
64  	private static final String SHUTDOWN_METHOD_NAME = "shutdown";
65  
66  	private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
67  
68  	private static Class<?> closeableInterface;
69  
70  	static {
71  		try {
72  			closeableInterface = ClassUtils.forName("java.lang.AutoCloseable",
73  					DisposableBeanAdapter.class.getClassLoader());
74  		}
75  		catch (ClassNotFoundException ex) {
76  			closeableInterface = Closeable.class;
77  		}
78  	}
79  
80  
81  	private final Object bean;
82  
83  	private final String beanName;
84  
85  	private final boolean invokeDisposableBean;
86  
87  	private final boolean nonPublicAccessAllowed;
88  
89  	private final AccessControlContext acc;
90  
91  	private String destroyMethodName;
92  
93  	private transient Method destroyMethod;
94  
95  	private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
96  
97  
98  	/**
99  	 * Create a new DisposableBeanAdapter for the given bean.
100 	 * @param bean the bean instance (never {@code null})
101 	 * @param beanName the name of the bean
102 	 * @param beanDefinition the merged bean definition
103 	 * @param postProcessors the List of BeanPostProcessors
104 	 * (potentially DestructionAwareBeanPostProcessor), if any
105 	 */
106 	public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition,
107 			List<BeanPostProcessor> postProcessors, AccessControlContext acc) {
108 
109 		Assert.notNull(bean, "Disposable bean must not be null");
110 		this.bean = bean;
111 		this.beanName = beanName;
112 		this.invokeDisposableBean =
113 				(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
114 		this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
115 		this.acc = acc;
116 		String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
117 		if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
118 				!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
119 			this.destroyMethodName = destroyMethodName;
120 			this.destroyMethod = determineDestroyMethod();
121 			if (this.destroyMethod == null) {
122 				if (beanDefinition.isEnforceDestroyMethod()) {
123 					throw new BeanDefinitionValidationException("Couldn't find a destroy method named '" +
124 							destroyMethodName + "' on bean with name '" + beanName + "'");
125 				}
126 			}
127 			else {
128 				Class<?>[] paramTypes = this.destroyMethod.getParameterTypes();
129 				if (paramTypes.length > 1) {
130 					throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
131 							beanName + "' has more than one parameter - not supported as destroy method");
132 				}
133 				else if (paramTypes.length == 1 && !paramTypes[0].equals(boolean.class)) {
134 					throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
135 							beanName + "' has a non-boolean parameter - not supported as destroy method");
136 				}
137 			}
138 		}
139 		this.beanPostProcessors = filterPostProcessors(postProcessors);
140 	}
141 
142 	/**
143 	 * Create a new DisposableBeanAdapter for the given bean.
144 	 * @param bean the bean instance (never {@code null})
145 	 * @param postProcessors the List of BeanPostProcessors
146 	 * (potentially DestructionAwareBeanPostProcessor), if any
147 	 */
148 	public DisposableBeanAdapter(Object bean, List<BeanPostProcessor> postProcessors, AccessControlContext acc) {
149 		Assert.notNull(bean, "Disposable bean must not be null");
150 		this.bean = bean;
151 		this.beanName = null;
152 		this.invokeDisposableBean = (this.bean instanceof DisposableBean);
153 		this.nonPublicAccessAllowed = true;
154 		this.acc = acc;
155 		this.beanPostProcessors = filterPostProcessors(postProcessors);
156 	}
157 
158 	/**
159 	 * Create a new DisposableBeanAdapter for the given bean.
160 	 */
161 	private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean,
162 			boolean nonPublicAccessAllowed, String destroyMethodName,
163 			List<DestructionAwareBeanPostProcessor> postProcessors) {
164 
165 		this.bean = bean;
166 		this.beanName = beanName;
167 		this.invokeDisposableBean = invokeDisposableBean;
168 		this.nonPublicAccessAllowed = nonPublicAccessAllowed;
169 		this.acc = null;
170 		this.destroyMethodName = destroyMethodName;
171 		this.beanPostProcessors = postProcessors;
172 	}
173 
174 
175 	/**
176 	 * If the current value of the given beanDefinition's "destroyMethodName" property is
177 	 * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
178 	 * Candidate methods are currently limited to public, no-arg methods named "close"
179 	 * (whether declared locally or inherited). The given BeanDefinition's
180 	 * "destroyMethodName" is updated to be null if no such method is found, otherwise set
181 	 * to the name of the inferred method. This constant serves as the default for the
182 	 * {@code @Bean#destroyMethod} attribute and the value of the constant may also be
183 	 * used in XML within the {@code <bean destroy-method="">} or {@code
184 	 * <beans default-destroy-method="">} attributes.
185 	 * <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
186 	 * interfaces, reflectively calling the "close" method on implementing beans as well.
187 	 */
188 	private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
189 		if (AbstractBeanDefinition.INFER_METHOD.equals(beanDefinition.getDestroyMethodName()) ||
190 				(beanDefinition.getDestroyMethodName() == null && closeableInterface.isInstance(bean))) {
191 			// Only perform destroy method inference or Closeable detection
192 			// in case of the bean not explicitly implementing DisposableBean
193 			if (!(bean instanceof DisposableBean)) {
194 				try {
195 					return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
196 				}
197 				catch (NoSuchMethodException ex) {
198 					try {
199 						return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
200 					}
201 					catch (NoSuchMethodException ex2) {
202 						// no candidate destroy method found
203 					}
204 				}
205 			}
206 			return null;
207 		}
208 		return beanDefinition.getDestroyMethodName();
209 	}
210 
211 	/**
212 	 * Search for all DestructionAwareBeanPostProcessors in the List.
213 	 * @param postProcessors the List to search
214 	 * @return the filtered List of DestructionAwareBeanPostProcessors
215 	 */
216 	private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> postProcessors) {
217 		List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
218 		if (postProcessors != null && !postProcessors.isEmpty()) {
219 			filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(postProcessors.size());
220 			for (BeanPostProcessor postProcessor : postProcessors) {
221 				if (postProcessor instanceof DestructionAwareBeanPostProcessor) {
222 					filteredPostProcessors.add((DestructionAwareBeanPostProcessor) postProcessor);
223 				}
224 			}
225 		}
226 		return filteredPostProcessors;
227 	}
228 
229 
230 	@Override
231 	public void run() {
232 		destroy();
233 	}
234 
235 	@Override
236 	public void destroy() {
237 		if (this.beanPostProcessors != null && !this.beanPostProcessors.isEmpty()) {
238 			for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
239 				processor.postProcessBeforeDestruction(this.bean, this.beanName);
240 			}
241 		}
242 
243 		if (this.invokeDisposableBean) {
244 			if (logger.isDebugEnabled()) {
245 				logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");
246 			}
247 			try {
248 				if (System.getSecurityManager() != null) {
249 					AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
250 						@Override
251 						public Object run() throws Exception {
252 							((DisposableBean) bean).destroy();
253 							return null;
254 						}
255 					}, acc);
256 				}
257 				else {
258 					((DisposableBean) bean).destroy();
259 				}
260 			}
261 			catch (Throwable ex) {
262 				String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
263 				if (logger.isDebugEnabled()) {
264 					logger.warn(msg, ex);
265 				}
266 				else {
267 					logger.warn(msg + ": " + ex);
268 				}
269 			}
270 		}
271 
272 		if (this.destroyMethod != null) {
273 			invokeCustomDestroyMethod(this.destroyMethod);
274 		}
275 		else if (this.destroyMethodName != null) {
276 			Method methodToCall = determineDestroyMethod();
277 			if (methodToCall != null) {
278 				invokeCustomDestroyMethod(methodToCall);
279 			}
280 		}
281 	}
282 
283 
284 	private Method determineDestroyMethod() {
285 		try {
286 			if (System.getSecurityManager() != null) {
287 				return AccessController.doPrivileged(new PrivilegedAction<Method>() {
288 					@Override
289 					public Method run() {
290 						return findDestroyMethod();
291 					}
292 				});
293 			}
294 			else {
295 				return findDestroyMethod();
296 			}
297 		}
298 		catch (IllegalArgumentException ex) {
299 			throw new BeanDefinitionValidationException("Couldn't find a unique destroy method on bean with name '" +
300 					this.beanName + ": " + ex.getMessage());
301 		}
302 	}
303 
304 	private Method findDestroyMethod() {
305 		return (this.nonPublicAccessAllowed ?
306 				BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), this.destroyMethodName) :
307 				BeanUtils.findMethodWithMinimalParameters(this.bean.getClass().getMethods(), this.destroyMethodName));
308 	}
309 
310 	/**
311 	 * Invoke the specified custom destroy method on the given bean.
312 	 * <p>This implementation invokes a no-arg method if found, else checking
313 	 * for a method with a single boolean argument (passing in "true",
314 	 * assuming a "force" parameter), else logging an error.
315 	 */
316 	private void invokeCustomDestroyMethod(final Method destroyMethod) {
317 		Class<?>[] paramTypes = destroyMethod.getParameterTypes();
318 		final Object[] args = new Object[paramTypes.length];
319 		if (paramTypes.length == 1) {
320 			args[0] = Boolean.TRUE;
321 		}
322 		if (logger.isDebugEnabled()) {
323 			logger.debug("Invoking destroy method '" + this.destroyMethodName +
324 					"' on bean with name '" + this.beanName + "'");
325 		}
326 		try {
327 			if (System.getSecurityManager() != null) {
328 				AccessController.doPrivileged(new PrivilegedAction<Object>() {
329 					@Override
330 					public Object run() {
331 						ReflectionUtils.makeAccessible(destroyMethod);
332 						return null;
333 					}
334 				});
335 				try {
336 					AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
337 						@Override
338 						public Object run() throws Exception {
339 							destroyMethod.invoke(bean, args);
340 							return null;
341 						}
342 					}, acc);
343 				}
344 				catch (PrivilegedActionException pax) {
345 					throw (InvocationTargetException) pax.getException();
346 				}
347 			}
348 			else {
349 				ReflectionUtils.makeAccessible(destroyMethod);
350 				destroyMethod.invoke(bean, args);
351 			}
352 		}
353 		catch (InvocationTargetException ex) {
354 			String msg = "Invocation of destroy method '" + this.destroyMethodName +
355 					"' failed on bean with name '" + this.beanName + "'";
356 			if (logger.isDebugEnabled()) {
357 				logger.warn(msg, ex.getTargetException());
358 			}
359 			else {
360 				logger.warn(msg + ": " + ex.getTargetException());
361 			}
362 		}
363 		catch (Throwable ex) {
364 			logger.error("Couldn't invoke destroy method '" + this.destroyMethodName +
365 					"' on bean with name '" + this.beanName + "'", ex);
366 		}
367 	}
368 
369 
370 	/**
371 	 * Serializes a copy of the state of this class,
372 	 * filtering out non-serializable BeanPostProcessors.
373 	 */
374 	protected Object writeReplace() {
375 		List<DestructionAwareBeanPostProcessor> serializablePostProcessors = null;
376 		if (this.beanPostProcessors != null) {
377 			serializablePostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>();
378 			for (DestructionAwareBeanPostProcessor postProcessor : this.beanPostProcessors) {
379 				if (postProcessor instanceof Serializable) {
380 					serializablePostProcessors.add(postProcessor);
381 				}
382 			}
383 		}
384 		return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean,
385 				this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
386 	}
387 
388 
389 	/**
390 	 * Check whether the given bean has any kind of destroy method to call.
391 	 * @param bean the bean instance
392 	 * @param beanDefinition the corresponding bean definition
393 	 */
394 	public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
395 		if (bean instanceof DisposableBean || closeableInterface.isInstance(bean)) {
396 			return true;
397 		}
398 		String destroyMethodName = beanDefinition.getDestroyMethodName();
399 		if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
400 			return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME);
401 		}
402 		return (destroyMethodName != null);
403 	}
404 
405 }