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:
parent
bac012f3e8
commit
406adb3381
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue