From 9410dff99cd193715de8cb0e242879018b775aec Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 4 Jun 2015 23:25:18 +0200 Subject: [PATCH] AsyncResult allows for exposing an execution exception Issue: SPR-13076 --- .../AsyncExecutionInterceptor.java | 6 ++- .../scheduling/annotation/AsyncResult.java | 53 +++++++++++++++++-- .../annotation/AsyncExecutionTests.java | 44 ++++++++++++++- 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index 1a89a40977..8b98bcce66 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.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. @@ -18,6 +18,7 @@ package org.springframework.aop.interceptor; import java.lang.reflect.Method; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; @@ -112,6 +113,9 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport return ((Future) result).get(); } } + catch (ExecutionException ex) { + handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments()); + } catch (Throwable ex) { handleError(ex, userDeclaredMethod, invocation.getArguments()); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java index 445fcb607f..ae6ecc4271 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.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. @@ -16,6 +16,8 @@ package org.springframework.scheduling.annotation; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.springframework.util.concurrent.FailureCallback; @@ -31,21 +33,37 @@ import org.springframework.util.concurrent.SuccessCallback; * plain {@link java.util.concurrent.Future}, along with the corresponding support * in {@code @Async} processing. * + *

As of Spring 4.2, this class also supports passing execution exceptions back + * to the caller. + * * @author Juergen Hoeller * @since 3.0 * @see Async + * @see #forValue(Object) + * @see #forExecutionException(Throwable) */ public class AsyncResult implements ListenableFuture { private final V value; + private final ExecutionException executionException; + /** * Create a new AsyncResult holder. * @param value the value to pass through */ public AsyncResult(V value) { + this(value, null); + } + + /** + * Create a new AsyncResult holder. + * @param value the value to pass through + */ + private AsyncResult(V value, ExecutionException ex) { this.value = value; + this.executionException = ex; } @@ -65,13 +83,16 @@ public class AsyncResult implements ListenableFuture { } @Override - public V get() { + public V get() throws ExecutionException { + if (this.executionException != null) { + throw this.executionException; + } return this.value; } @Override - public V get(long timeout, TimeUnit unit) { - return this.value; + public V get(long timeout, TimeUnit unit) throws ExecutionException { + return get(); } @Override @@ -89,4 +110,28 @@ public class AsyncResult implements ListenableFuture { } } + + /** + * Create a new async result which exposes the given value from {@link Future#get()}. + * @param value the value to expose + * @since 4.2 + * @see Future#get() + */ + public static ListenableFuture forValue(V value) { + return new AsyncResult(value, null); + } + + /** + * Create a new async result which exposes the given exception as an + * {@link ExecutionException} from {@link Future#get()}. + * @param ex the exception to expose (either an pre-built {@link ExecutionException} + * or a cause to be wrapped in an {@link ExecutionException}) + * @since 4.2 + * @see ExecutionException + */ + public static ListenableFuture forExecutionException(Throwable ex) { + return new AsyncResult(null, + (ex instanceof ExecutionException ? (ExecutionException) ex : new ExecutionException(ex))); + } + } diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index 2e503211ef..9562fed9e4 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.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. @@ -16,10 +16,12 @@ package org.springframework.scheduling.annotation; +import java.io.IOException; import java.io.Serializable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import org.aopalliance.intercept.MethodInterceptor; @@ -61,6 +63,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + AsyncMethodBean asyncTest = context.getBean("asyncTest", AsyncMethodBean.class); asyncTest.doNothing(5); asyncTest.doSomething(10); @@ -68,6 +71,24 @@ public class AsyncExecutionTests { assertEquals("20", future.get()); ListenableFuture listenableFuture = asyncTest.returnSomethingListenable(20); assertEquals("20", listenableFuture.get()); + + future = asyncTest.returnSomething(0); + try { + future.get(); + fail("Should have thrown ExecutionException"); + } + catch (ExecutionException ex) { + assertTrue(ex.getCause() instanceof IllegalArgumentException); + } + + future = asyncTest.returnSomething(-1); + try { + future.get(); + fail("Should have thrown ExecutionException"); + } + catch (ExecutionException ex) { + assertTrue(ex.getCause() instanceof IOException); + } } @Test @@ -96,6 +117,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("e1", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); context.registerBeanDefinition("e2", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); context.refresh(); + AsyncMethodWithQualifierBean asyncTest = context.getBean("asyncTest", AsyncMethodWithQualifierBean.class); asyncTest.doNothing(5); asyncTest.doSomething(10); @@ -116,6 +138,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("e1", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); context.registerBeanDefinition("e2", new RootBeanDefinition(ThreadPoolTaskExecutor.class)); context.refresh(); + SimpleInterface asyncTest = context.getBean("asyncTest", SimpleInterface.class); asyncTest.doNothing(5); asyncTest.doSomething(10); @@ -133,6 +156,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + AsyncClassBean asyncTest = context.getBean("asyncTest", AsyncClassBean.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -148,6 +172,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBean.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + AsyncClassBean asyncTest = context.getBean("asyncTest", AsyncClassBean.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -162,6 +187,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + RegularInterface asyncTest = context.getBean("asyncTest", RegularInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -175,6 +201,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBeanWithInterface.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + RegularInterface asyncTest = context.getBean("asyncTest", RegularInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -189,6 +216,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -202,6 +230,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncInterfaceBean.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -216,6 +245,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -229,6 +259,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(DynamicAsyncInterfaceBean.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -243,6 +274,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); context.refresh(); + AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class); asyncTest.doNothing(5); asyncTest.doSomething(10); @@ -257,6 +289,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodsInterfaceBean.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class); asyncTest.doNothing(5); asyncTest.doSomething(10); @@ -271,6 +304,7 @@ public class AsyncExecutionTests { context.registerBeanDefinition("asyncTest", new RootBeanDefinition(DynamicAsyncMethodsInterfaceBean.class)); context.registerBeanDefinition("asyncProcessor", new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class)); context.refresh(); + AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class); asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); @@ -351,7 +385,13 @@ public class AsyncExecutionTests { @Async public Future returnSomething(int i) { assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); - return new AsyncResult(Integer.toString(i)); + if (i == 0) { + throw new IllegalArgumentException(); + } + else if (i < 0) { + return AsyncResult.forExecutionException(new IOException()); + } + return AsyncResult.forValue(Integer.toString(i)); } @Async