1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
100
101
102
103
104
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
144
145
146
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
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
177
178
179
180
181
182
183
184
185
186
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
192
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
203 }
204 }
205 }
206 return null;
207 }
208 return beanDefinition.getDestroyMethodName();
209 }
210
211
212
213
214
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
312
313
314
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
372
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
391
392
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 }