21
21
import java .lang .reflect .Method ;
22
22
import java .util .ArrayList ;
23
23
import java .util .List ;
24
+ import java .util .concurrent .CountDownLatch ;
25
+ import java .util .concurrent .ExecutionException ;
26
+ import java .util .concurrent .Future ;
24
27
25
28
import org .apache .commons .logging .Log ;
26
29
import org .apache .commons .logging .LogFactory ;
30
+ import org .reactivestreams .Subscriber ;
31
+ import org .reactivestreams .Subscription ;
27
32
28
33
import org .springframework .beans .BeanUtils ;
29
34
import org .springframework .beans .factory .DisposableBean ;
30
35
import org .springframework .beans .factory .config .DestructionAwareBeanPostProcessor ;
36
+ import org .springframework .core .ReactiveAdapter ;
37
+ import org .springframework .core .ReactiveAdapterRegistry ;
31
38
import org .springframework .lang .Nullable ;
32
39
import org .springframework .util .Assert ;
33
40
import org .springframework .util .ClassUtils ;
@@ -65,8 +72,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
65
72
66
73
private static final String SHUTDOWN_METHOD_NAME = "shutdown" ;
67
74
75
+
68
76
private static final Log logger = LogFactory .getLog (DisposableBeanAdapter .class );
69
77
78
+ private static final boolean reactiveStreamsPresent = ClassUtils .isPresent (
79
+ "org.reactivestreams.Publisher" , DisposableBeanAdapter .class .getClassLoader ());
80
+
70
81
71
82
private final Object bean ;
72
83
@@ -240,7 +251,7 @@ else if (this.destroyMethods != null) {
240
251
}
241
252
}
242
253
else if (this .destroyMethodNames != null ) {
243
- for (String destroyMethodName : this .destroyMethodNames ) {
254
+ for (String destroyMethodName : this .destroyMethodNames ) {
244
255
Method destroyMethod = determineDestroyMethod (destroyMethodName );
245
256
if (destroyMethod != null ) {
246
257
invokeCustomDestroyMethod (
@@ -287,32 +298,40 @@ private Method findDestroyMethod(Class<?> clazz, String name) {
287
298
* assuming a "force" parameter), else logging an error.
288
299
*/
289
300
private void invokeCustomDestroyMethod (Method destroyMethod ) {
301
+ if (logger .isTraceEnabled ()) {
302
+ logger .trace ("Invoking custom destroy method '" + destroyMethod .getName () +
303
+ "' on bean with name '" + this .beanName + "': " + destroyMethod );
304
+ }
305
+
290
306
int paramCount = destroyMethod .getParameterCount ();
291
- final Object [] args = new Object [paramCount ];
307
+ Object [] args = new Object [paramCount ];
292
308
if (paramCount == 1 ) {
293
309
args [0 ] = Boolean .TRUE ;
294
310
}
295
- if (logger .isTraceEnabled ()) {
296
- logger .trace ("Invoking custom destroy method '" + destroyMethod .getName () +
297
- "' on bean with name '" + this .beanName + "'" );
298
- }
311
+
299
312
try {
300
313
ReflectionUtils .makeAccessible (destroyMethod );
301
- destroyMethod .invoke (this .bean , args );
302
- }
303
- catch (InvocationTargetException ex ) {
304
- if (logger .isWarnEnabled ()) {
305
- String msg = "Custom destroy method '" + destroyMethod .getName () + "' on bean with name '" +
306
- this .beanName + "' threw an exception" ;
314
+ Object returnValue = destroyMethod .invoke (this .bean , args );
315
+
316
+ if (returnValue == null ) {
317
+ // Regular case: a void method
318
+ logDestroyMethodCompletion (destroyMethod , false );
319
+ }
320
+ else if (returnValue instanceof Future <?> future ) {
321
+ // An async task: await its completion.
322
+ future .get ();
323
+ logDestroyMethodCompletion (destroyMethod , true );
324
+ }
325
+ else if (!reactiveStreamsPresent || !new ReactiveDestroyMethodHandler ().await (destroyMethod , returnValue )) {
307
326
if (logger .isDebugEnabled ()) {
308
- // Log at warn level like below but add the exception stacktrace only with debug level
309
- logger .warn (msg , ex .getTargetException ());
310
- }
311
- else {
312
- logger .warn (msg + ": " + ex .getTargetException ());
327
+ logger .debug ("Unknown return value type from custom destroy method '" + destroyMethod .getName () +
328
+ "' on bean with name '" + this .beanName + "': " + returnValue .getClass ());
313
329
}
314
330
}
315
331
}
332
+ catch (InvocationTargetException | ExecutionException ex ) {
333
+ logDestroyMethodException (destroyMethod , ex .getCause ());
334
+ }
316
335
catch (Throwable ex ) {
317
336
if (logger .isWarnEnabled ()) {
318
337
logger .warn ("Failed to invoke custom destroy method '" + destroyMethod .getName () +
@@ -321,6 +340,27 @@ private void invokeCustomDestroyMethod(Method destroyMethod) {
321
340
}
322
341
}
323
342
343
+ void logDestroyMethodException (Method destroyMethod , Throwable ex ) {
344
+ if (logger .isWarnEnabled ()) {
345
+ String msg = "Custom destroy method '" + destroyMethod .getName () + "' on bean with name '" +
346
+ this .beanName + "' propagated an exception" ;
347
+ if (logger .isDebugEnabled ()) {
348
+ // Log at warn level like below but add the exception stacktrace only with debug level
349
+ logger .warn (msg , ex );
350
+ }
351
+ else {
352
+ logger .warn (msg + ": " + ex );
353
+ }
354
+ }
355
+ }
356
+
357
+ void logDestroyMethodCompletion (Method destroyMethod , boolean async ) {
358
+ if (logger .isDebugEnabled ()) {
359
+ logger .debug ("Custom destroy method '" + destroyMethod .getName () +
360
+ "' on bean with name '" + this .beanName + "' completed" + (async ? " asynchronously" : "" ));
361
+ }
362
+ }
363
+
324
364
325
365
/**
326
366
* Serializes a copy of the state of this class,
@@ -443,4 +483,59 @@ private static List<DestructionAwareBeanPostProcessor> filterPostProcessors(
443
483
return filteredPostProcessors ;
444
484
}
445
485
486
+
487
+ /**
488
+ * Inner class to avoid a hard dependency on the Reactive Streams API at runtime.
489
+ */
490
+ private class ReactiveDestroyMethodHandler {
491
+
492
+ public boolean await (Method destroyMethod , Object returnValue ) throws InterruptedException {
493
+ ReactiveAdapter adapter = ReactiveAdapterRegistry .getSharedInstance ().getAdapter (returnValue .getClass ());
494
+ if (adapter != null ) {
495
+ CountDownLatch latch = new CountDownLatch (1 );
496
+ adapter .toPublisher (returnValue ).subscribe (new DestroyMethodSubscriber (destroyMethod , latch ));
497
+ latch .await ();
498
+ return true ;
499
+ }
500
+ return false ;
501
+ }
502
+ }
503
+
504
+
505
+ /**
506
+ * Reactive Streams Subscriber for destroy method completion.
507
+ */
508
+ private class DestroyMethodSubscriber implements Subscriber <Object > {
509
+
510
+ private final Method destroyMethod ;
511
+
512
+ private final CountDownLatch latch ;
513
+
514
+ public DestroyMethodSubscriber (Method destroyMethod , CountDownLatch latch ) {
515
+ this .destroyMethod = destroyMethod ;
516
+ this .latch = latch ;
517
+ }
518
+
519
+ @ Override
520
+ public void onSubscribe (Subscription s ) {
521
+ s .request (Integer .MAX_VALUE );
522
+ }
523
+
524
+ @ Override
525
+ public void onNext (Object o ) {
526
+ }
527
+
528
+ @ Override
529
+ public void onError (Throwable t ) {
530
+ this .latch .countDown ();
531
+ logDestroyMethodException (this .destroyMethod , t );
532
+ }
533
+
534
+ @ Override
535
+ public void onComplete () {
536
+ this .latch .countDown ();
537
+ logDestroyMethodCompletion (this .destroyMethod , true );
538
+ }
539
+ }
540
+
446
541
}
0 commit comments