Skip to content

Commit 406adb3

Browse files
committed
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
1 parent bac012f commit 406adb3

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -165,7 +165,7 @@ else if (executorToUse == null) {
165165
* @param params the parameters used to invoke the method
166166
*/
167167
protected void handleError(Throwable ex, Method method, Object... params) throws Exception {
168-
if (method.getReturnType().isAssignableFrom(Future.class)) {
168+
if (Future.class.isAssignableFrom(method.getReturnType())) {
169169
ReflectionUtils.rethrowException(ex);
170170
}
171171
else {

spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,15 +25,20 @@
2525

2626
import org.junit.Test;
2727

28+
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
2829
import org.springframework.aop.support.AopUtils;
2930
import org.springframework.beans.factory.config.BeanDefinition;
3031
import org.springframework.beans.factory.support.RootBeanDefinition;
3132
import org.springframework.context.ConfigurableApplicationContext;
33+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
3236
import org.springframework.context.support.GenericXmlApplicationContext;
3337
import org.springframework.context.support.StaticApplicationContext;
3438
import org.springframework.core.io.ClassPathResource;
3539
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
3640
import org.springframework.util.ReflectionUtils;
41+
import org.springframework.util.concurrent.ListenableFuture;
3742

3843
import static org.junit.Assert.*;
3944

@@ -106,10 +111,32 @@ public void configuredThroughNamespace() {
106111

107112
@Test
108113
public void handleExceptionWithFuture() {
109-
ConfigurableApplicationContext context = initContext(
110-
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
114+
ConfigurableApplicationContext context =
115+
new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class);
111116
ITestBean testBean = context.getBean("target", ITestBean.class);
112-
final Future<Object> result = testBean.failWithFuture();
117+
118+
TestableAsyncUncaughtExceptionHandler exceptionHandler =
119+
context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class);
120+
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
121+
Future<Object> result = testBean.failWithFuture();
122+
assertFutureWithException(result, exceptionHandler);
123+
}
124+
125+
@Test
126+
public void handleExceptionWithListenableFuture() {
127+
ConfigurableApplicationContext context =
128+
new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class);
129+
ITestBean testBean = context.getBean("target", ITestBean.class);
130+
131+
TestableAsyncUncaughtExceptionHandler exceptionHandler =
132+
context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class);
133+
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
134+
Future<Object> result = testBean.failWithListenableFuture();
135+
assertFutureWithException(result, exceptionHandler);
136+
}
137+
138+
private void assertFutureWithException(Future<Object> result,
139+
TestableAsyncUncaughtExceptionHandler exceptionHandler) {
113140

114141
try {
115142
result.get();
@@ -121,6 +148,7 @@ public void handleExceptionWithFuture() {
121148
// expected
122149
assertEquals("Wrong exception cause", UnsupportedOperationException.class, ex.getCause().getClass());
123150
}
151+
assertFalse("handler should never be called with Future return type", exceptionHandler.isCalled());
124152
}
125153

126154
@Test
@@ -173,14 +201,16 @@ private ConfigurableApplicationContext initContext(BeanDefinition asyncAnnotatio
173201
}
174202

175203

176-
private static interface ITestBean {
204+
private interface ITestBean {
177205

178206
Thread getThread();
179207

180208
void test();
181209

182210
Future<Object> failWithFuture();
183211

212+
ListenableFuture<Object> failWithListenableFuture();
213+
184214
void failWithVoid();
185215

186216
void await(long timeout);
@@ -206,11 +236,19 @@ public void test() {
206236
}
207237

208238
@Async
239+
@Override
209240
public Future<Object> failWithFuture() {
210241
throw new UnsupportedOperationException("failWithFuture");
211242
}
212243

213244
@Async
245+
@Override
246+
public ListenableFuture<Object> failWithListenableFuture() {
247+
throw new UnsupportedOperationException("failWithListenableFuture");
248+
}
249+
250+
@Async
251+
@Override
214252
public void failWithVoid() {
215253
throw new UnsupportedOperationException("failWithVoid");
216254
}
@@ -234,4 +272,25 @@ public void execute(Runnable r) {
234272
}
235273
}
236274

275+
@Configuration
276+
@EnableAsync
277+
static class ConfigWithExceptionHandler extends AsyncConfigurerSupport {
278+
279+
@Bean
280+
public ITestBean target() {
281+
return new TestBean();
282+
}
283+
284+
@Override
285+
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
286+
return exceptionHandler();
287+
}
288+
289+
@Bean
290+
public TestableAsyncUncaughtExceptionHandler exceptionHandler() {
291+
return new TestableAsyncUncaughtExceptionHandler();
292+
}
293+
}
294+
295+
237296
}

0 commit comments

Comments
 (0)