From 8f8a85912a16f57ed204ca0c12423fedbf4ebe30 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 9 Mar 2015 09:57:53 +0100 Subject: [PATCH] Call AsyncUncaughtExceptionHandler when necessary If a sub-class of Future (such as ListenableFuture) is used as a return type and an exception is thrown, the AsyncUncaughtExceptionHandler is called. Now checking for any Future implementation instead of a faulty strict matching. Issue: SPR-12797 --- .../AsyncExecutionAspectSupport.java | 4 +- ...AsyncAnnotationBeanPostProcessorTests.java | 69 +++++++++++++++++-- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 9f6b3c20c6..ebd59e5fc9 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -165,7 +165,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * @param params the parameters used to invoke the method */ protected void handleError(Throwable ex, Method method, Object... params) throws Exception { - if (method.getReturnType().isAssignableFrom(Future.class)) { + if (Future.class.isAssignableFrom(method.getReturnType())) { ReflectionUtils.rethrowException(ex); } else { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index f7d3f588ea..75ebf30697 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,15 +25,20 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ReflectionUtils; +import org.springframework.util.concurrent.ListenableFuture; import static org.junit.Assert.*; @@ -106,10 +111,32 @@ public class AsyncAnnotationBeanPostProcessorTests { @Test public void handleExceptionWithFuture() { - ConfigurableApplicationContext context = initContext( - new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); + ConfigurableApplicationContext context = + new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class); ITestBean testBean = context.getBean("target", ITestBean.class); - final Future result = testBean.failWithFuture(); + + TestableAsyncUncaughtExceptionHandler exceptionHandler = + context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); + assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); + Future result = testBean.failWithFuture(); + assertFutureWithException(result, exceptionHandler); + } + + @Test + public void handleExceptionWithListenableFuture() { + ConfigurableApplicationContext context = + new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class); + ITestBean testBean = context.getBean("target", ITestBean.class); + + TestableAsyncUncaughtExceptionHandler exceptionHandler = + context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); + assertFalse("handler should not have been called yet", exceptionHandler.isCalled()); + Future result = testBean.failWithListenableFuture(); + assertFutureWithException(result, exceptionHandler); + } + + private void assertFutureWithException(Future result, + TestableAsyncUncaughtExceptionHandler exceptionHandler) { try { result.get(); @@ -121,6 +148,7 @@ public class AsyncAnnotationBeanPostProcessorTests { // expected assertEquals("Wrong exception cause", UnsupportedOperationException.class, ex.getCause().getClass()); } + assertFalse("handler should never be called with Future return type", exceptionHandler.isCalled()); } @Test @@ -173,7 +201,7 @@ public class AsyncAnnotationBeanPostProcessorTests { } - private static interface ITestBean { + private interface ITestBean { Thread getThread(); @@ -181,6 +209,8 @@ public class AsyncAnnotationBeanPostProcessorTests { Future failWithFuture(); + ListenableFuture failWithListenableFuture(); + void failWithVoid(); void await(long timeout); @@ -206,11 +236,19 @@ public class AsyncAnnotationBeanPostProcessorTests { } @Async + @Override public Future failWithFuture() { throw new UnsupportedOperationException("failWithFuture"); } @Async + @Override + public ListenableFuture failWithListenableFuture() { + throw new UnsupportedOperationException("failWithListenableFuture"); + } + + @Async + @Override public void failWithVoid() { throw new UnsupportedOperationException("failWithVoid"); } @@ -234,4 +272,25 @@ public class AsyncAnnotationBeanPostProcessorTests { } } + @Configuration + @EnableAsync + static class ConfigWithExceptionHandler extends AsyncConfigurerSupport { + + @Bean + public ITestBean target() { + return new TestBean(); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return exceptionHandler(); + } + + @Bean + public TestableAsyncUncaughtExceptionHandler exceptionHandler() { + return new TestableAsyncUncaughtExceptionHandler(); + } + } + + }