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
This commit is contained in:
Stephane Nicoll 2015-03-09 09:57:53 +01:00
parent bac012f3e8
commit 406adb3381
2 changed files with 66 additions and 7 deletions

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * @param params the parameters used to invoke the method
*/ */
protected void handleError(Throwable ex, Method method, Object... params) throws Exception { 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); ReflectionUtils.rethrowException(ex);
} }
else { else {

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.junit.Test;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext; 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.GenericXmlApplicationContext;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.concurrent.ListenableFuture;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -106,10 +111,32 @@ public class AsyncAnnotationBeanPostProcessorTests {
@Test @Test
public void handleExceptionWithFuture() { public void handleExceptionWithFuture() {
ConfigurableApplicationContext context = initContext( ConfigurableApplicationContext context =
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class);
ITestBean testBean = context.getBean("target", ITestBean.class); ITestBean testBean = context.getBean("target", ITestBean.class);
final Future<Object> result = testBean.failWithFuture();
TestableAsyncUncaughtExceptionHandler exceptionHandler =
context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class);
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
Future<Object> 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<Object> result = testBean.failWithListenableFuture();
assertFutureWithException(result, exceptionHandler);
}
private void assertFutureWithException(Future<Object> result,
TestableAsyncUncaughtExceptionHandler exceptionHandler) {
try { try {
result.get(); result.get();
@ -121,6 +148,7 @@ public class AsyncAnnotationBeanPostProcessorTests {
// expected // expected
assertEquals("Wrong exception cause", UnsupportedOperationException.class, ex.getCause().getClass()); assertEquals("Wrong exception cause", UnsupportedOperationException.class, ex.getCause().getClass());
} }
assertFalse("handler should never be called with Future return type", exceptionHandler.isCalled());
} }
@Test @Test
@ -173,7 +201,7 @@ public class AsyncAnnotationBeanPostProcessorTests {
} }
private static interface ITestBean { private interface ITestBean {
Thread getThread(); Thread getThread();
@ -181,6 +209,8 @@ public class AsyncAnnotationBeanPostProcessorTests {
Future<Object> failWithFuture(); Future<Object> failWithFuture();
ListenableFuture<Object> failWithListenableFuture();
void failWithVoid(); void failWithVoid();
void await(long timeout); void await(long timeout);
@ -206,11 +236,19 @@ public class AsyncAnnotationBeanPostProcessorTests {
} }
@Async @Async
@Override
public Future<Object> failWithFuture() { public Future<Object> failWithFuture() {
throw new UnsupportedOperationException("failWithFuture"); throw new UnsupportedOperationException("failWithFuture");
} }
@Async @Async
@Override
public ListenableFuture<Object> failWithListenableFuture() {
throw new UnsupportedOperationException("failWithListenableFuture");
}
@Async
@Override
public void failWithVoid() { public void failWithVoid() {
throw new UnsupportedOperationException("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();
}
}
} }