Add exception handling of asynchronous method

Prior to this commit, an exception thrown by an @Async void method
was not further processed as there is no way to transmit that
exception to the caller.

The AsyncUncaughtExceptionHandler is a new strategy interface that
can be implemented to handle unexpected exception thrown during the
invocation of such asynchronous method.

The handler can be specified using either the XML namespace or by
implementing the AsyncConfigurer interface with the EnableAsync
annotation.

Issue: SPR-8995
This commit is contained in:
Stephane Nicoll
2014-01-21 16:25:19 +01:00
parent 59703981c4
commit db23ec733b
22 changed files with 2182 additions and 59 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -23,6 +23,8 @@ import java.util.concurrent.Future;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.BridgeMethodResolver;
@@ -46,12 +48,18 @@ import org.springframework.util.ReflectionUtils;
* (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult}
* or EJB 3.1's {@code javax.ejb.AsyncResult}).
*
* <p>When the return type is {@code java.util.concurrent.Future}, any exception thrown
* during the execution can be accessed and managed by the caller. With {@code void}
* return type however, such exceptions cannot be transmitted back. In that case an
* {@link AsyncUncaughtExceptionHandler} can be registered to process such exceptions.
*
* <p>As of Spring 3.1.2 the {@code AnnotationAsyncExecutionInterceptor} subclass is
* preferred for use due to its support for executor qualification in conjunction with
* Spring's {@code @Async} annotation.
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.0
* @see org.springframework.scheduling.annotation.Async
* @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
@@ -60,15 +68,35 @@ import org.springframework.util.ReflectionUtils;
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
implements MethodInterceptor, Ordered {
private final Log logger = LogFactory.getLog(getClass());
private AsyncUncaughtExceptionHandler exceptionHandler;
/**
* Create a new {@code AsyncExecutionInterceptor}.
* @param executor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor}
* @param defaultExecutor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor}
* or {@link java.util.concurrent.ExecutorService}) to delegate to.
* @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use
*/
public AsyncExecutionInterceptor(Executor executor) {
super(executor);
public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) {
super(defaultExecutor);
this.exceptionHandler = exceptionHandler;
}
/**
* Create a new instance with a default {@link AsyncUncaughtExceptionHandler}.
*/
public AsyncExecutionInterceptor(Executor defaultExecutor) {
this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler());
}
/**
* Supply the {@link AsyncUncaughtExceptionHandler} to use to handle exceptions
* thrown by invoking asynchronous methods with a {@code void} return type.
*/
public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
/**
* Intercept the given method invocation, submit the actual calling of the method to
@@ -80,8 +108,8 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
Method tmp = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method specificMethod = BridgeMethodResolver.findBridgedMethod(tmp);
AsyncTaskExecutor executor = determineAsyncExecutor(specificMethod);
if (executor == null) {
@@ -100,7 +128,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
}
}
catch (Throwable ex) {
ReflectionUtils.rethrowException(ex);
handleError(ex, specificMethod, invocation.getArguments());
}
return null;
}
@@ -114,6 +142,34 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport
}
}
/**
* Handles a fatal error thrown while asynchronously invoking the specified
* {@link Method}.
* <p>If the return type of the method is a {@link Future} object, the original
* exception can be propagated by just throwing it at the higher level. However,
* for all other cases, the exception will not be transmitted back to the client.
* In that later case, the current {@link AsyncUncaughtExceptionHandler} will be
* used to manage such exception.
*
* @param ex the exception to handle
* @param method the method that was invoked
* @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)) {
ReflectionUtils.rethrowException(ex);
}
else { // Could not transmit the exception to the caller with default executor
try {
exceptionHandler.handleUncaughtException(ex, method, params);
}
catch (Exception e) {
logger.error("exception handler has thrown an unexpected " +
"exception while invoking '" + method.toGenericString() + "'", e);
}
}
}
/**
* This implementation is a no-op for compatibility in Spring 3.1.2.
* Subclasses may override to provide support for extracting qualifier information,

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.interceptor;
import java.lang.reflect.Method;
/**
* A strategy for handling uncaught exception thrown by asynchronous methods.
*
* <p>An asynchronous method usually returns a {@link java.util.concurrent.Future}
* instance that gives access to the underlying exception. When the method
* does not provide that return type, this handler can be used to managed such
* uncaught exceptions.
*
* @author Stephane Nicoll
* @since 4.1
*/
public interface AsyncUncaughtExceptionHandler {
/**
* Handle the given uncaught error thrown while processing
* an asynchronous method.
* @param ex the exception thrown by invoking the async operation
* @param method the async operation
* @param params the parameters used to invoked the method
*/
void handleUncaughtException(Throwable ex, Method method, Object... params);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.interceptor;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A default {@link AsyncUncaughtExceptionHandler} that simply logs the exception.
*
* @author Stephane Nicoll
* @since 4.1
*/
public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
private final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
if (logger.isErrorEnabled()) {
logger.error(String.format("Unexpected error occurred invoking async " +
"method '%s'.", method), ex);
}
}
}

View File

@@ -4,14 +4,16 @@ http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springfram
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-4.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.0.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-4.1.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-4.1.xsd
http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/tool"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/tool"
elementFormDefault="qualified">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the tool support annotations for Spring's configuration namespaces.
Used in other namespace XSD files; not intended for direct use in config files.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="annotation">
<xsd:complexType>
<xsd:sequence minOccurs="0">
<xsd:element name="expected-type" type="typedParameterType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="assignable-to" type="assignableToType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="exports" type="exportsType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="registers-scope" type="registersScopeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="expected-method" type="expectedMethodType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="kind" default="direct">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ref"/>
<xsd:enumeration value="direct"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="typedParameterType">
<xsd:attribute name="type" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="assignableToType">
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="restriction" default="both">
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="both"/>
<xsd:enumeration value="interface-only"/>
<xsd:enumeration value="class-only"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="expectedMethodType">
<xsd:attribute name="type" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines an XPath query that can be executed against the node annotated with this
type to determine the class for which the this method is valid
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type-ref" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines an XPath query that can be executed against the node annotated with this
type to determine a referenced bean (by id or alias) for which the given method is valid
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines an AspectJ method execution pointcut expressions that matches valid methods
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="exportsType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Indicates that an annotated type exports an application visible component.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="type" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of the exported component. May be null if the type is not known until runtime.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="identifier" type="xsd:string" default="@id">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines an XPath query that can be executed against the node annotated with this
type to determine the identifier of any exported component.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="registersScopeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the name of a custom bean scope that the annotated type registers, e.g. "conversation".
Such a scope will be available in addition to the standard "singleton" and "prototype" scopes
(plus "request", "session" and "globalSession" in a web application environment).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -19,6 +19,7 @@ package org.springframework.scheduling.annotation;
import java.util.Collection;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
@@ -32,6 +33,7 @@ import org.springframework.util.CollectionUtils;
* Spring's asynchronous method execution capability.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
* @see EnableAsync
*/
@@ -41,6 +43,7 @@ public abstract class AbstractAsyncConfiguration implements ImportAware {
protected AnnotationAttributes enableAsync;
protected Executor executor;
protected AsyncUncaughtExceptionHandler exceptionHandler;
@Override
@@ -64,6 +67,7 @@ public abstract class AbstractAsyncConfiguration implements ImportAware {
}
AsyncConfigurer configurer = configurers.iterator().next();
this.executor = configurer.getAsyncExecutor();
this.exceptionHandler = configurer.getAsyncUncaughtExceptionHandler();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -20,6 +20,8 @@ import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncExecutionInterceptor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.core.annotation.AnnotationUtils;
/**
@@ -30,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils;
* declaring class level. See {@link #getExecutorQualifier(Method)} for details.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1.2
* @see org.springframework.scheduling.annotation.Async
* @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor
@@ -40,9 +43,23 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
* @param defaultExecutor the executor to be used by default if no more specific
* executor has been qualified at the method level using {@link Async#value()}
* @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
* handle exceptions thrown by asynchronous method executions with {@code void}
* return type
*/
public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor,
AsyncUncaughtExceptionHandler exceptionHandler) {
super(defaultExecutor, exceptionHandler);
}
/**
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor
* and a simple {@link AsyncUncaughtExceptionHandler}.
* @param defaultExecutor the executor to be used by default if no more specific
* executor has been qualified at the method level using {@link Async#value()}
*/
public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
super(defaultExecutor);
this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler());
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,6 +25,8 @@ import java.util.concurrent.Executor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
@@ -53,6 +55,8 @@ import org.springframework.util.Assert;
@SuppressWarnings("serial")
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private AsyncUncaughtExceptionHandler exceptionHandler;
private Advice advice;
private Pointcut pointcut;
@@ -62,15 +66,17 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
* Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
*/
public AsyncAnnotationAdvisor() {
this(new SimpleAsyncTaskExecutor());
this(null, null);
}
/**
* Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
* @param executor the task executor to use for asynchronous methods
* @param exceptionHandler the {@link org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler} to use to
* handle unexpected exception thrown by asynchronous method executions
*/
@SuppressWarnings("unchecked")
public AsyncAnnotationAdvisor(Executor executor) {
public AsyncAnnotationAdvisor(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) {
Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(2);
asyncAnnotationTypes.add(Async.class);
ClassLoader cl = AsyncAnnotationAdvisor.class.getClassLoader();
@@ -80,7 +86,15 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
catch (ClassNotFoundException ex) {
// If EJB 3.1 API not present, simply ignore.
}
this.advice = buildAdvice(executor);
if (executor == null) {
executor = new SimpleAsyncTaskExecutor();
}
if (exceptionHandler != null) {
this.exceptionHandler = exceptionHandler;
} else {
this.exceptionHandler = new SimpleAsyncUncaughtExceptionHandler();
}
this.advice = buildAdvice(executor, this.exceptionHandler);
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
@@ -89,7 +103,7 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
* Specify the default task executor to use for asynchronous methods.
*/
public void setTaskExecutor(Executor executor) {
this.advice = buildAdvice(executor);
this.advice = buildAdvice(executor, exceptionHandler);
}
/**
@@ -130,8 +144,8 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
}
protected Advice buildAdvice(Executor executor) {
return new AnnotationAsyncExecutionInterceptor(executor);
protected Advice buildAdvice(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) {
return new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.util.concurrent.Executor;
import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.task.TaskExecutor;
@@ -38,11 +39,17 @@ import org.springframework.util.Assert;
* processor will detect both Spring's {@link Async @Async} annotation as well
* as the EJB 3.1 {@code javax.ejb.Asynchronous} annotation.
*
* <p>For methods having a {@code void} return type, any exception thrown
* during the asynchronous method invocation cannot be accessed by the
* caller. An {@link AsyncUncaughtExceptionHandler} can be specified to handle
* these cases.
*
* <p>Note: The underlying async advisor applies before existing advisors by default,
* in order to switch to async execution as early as possible in the invocation chain.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.0
* @see Async
* @see AsyncAnnotationAdvisor
@@ -55,6 +62,7 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostPr
private Class<? extends Annotation> asyncAnnotationType;
private Executor executor;
private AsyncUncaughtExceptionHandler exceptionHandler;
public AsyncAnnotationBeanPostProcessor() {
@@ -82,10 +90,17 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostPr
this.executor = executor;
}
/**
* Set the {@link AsyncUncaughtExceptionHandler} to use to handle uncaught
* exceptions thrown by asynchronous method executions.
*/
public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
AsyncAnnotationAdvisor advisor = (this.executor != null ?
new AsyncAnnotationAdvisor(this.executor) : new AsyncAnnotationAdvisor());
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2014 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,17 +18,28 @@ package org.springframework.scheduling.annotation;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
/**
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableAsync} that wish to customize the
* {@link Executor} instance used when processing async method invocations.
* {@link Executor} instance used when processing async method invocations or the
* {@link AsyncUncaughtExceptionHandler} instance used to process exception thrown from
* async method with {@code void} return type.
*
* <p>Consider using {@link AsyncConfigurerSupport} providing default implementations for
* both methods if only one element needs to be customized. Furthermore, backward compatibility
* of this interface will be insured in case new customization options are introduced
* in the future.
*
* <p>See @{@link EnableAsync} for usage examples.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
* @see AbstractAsyncConfiguration
* @see EnableAsync
* @see AsyncConfigurerSupport
*/
public interface AsyncConfigurer {
@@ -38,4 +49,11 @@ public interface AsyncConfigurer {
*/
Executor getAsyncExecutor();
/**
* The {@link AsyncUncaughtExceptionHandler} instance to be used
* when an exception is thrown during an asynchronous method execution
* with {@code void} return type.
*/
AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler();
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.annotation;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
/**
* A convenience {@link AsyncConfigurer} that implements all methods
* so that the defaults are used. Provides a backward compatible alternative
* of implementing {@link AsyncConfigurer} directly.
*
* @author Stephane Nicoll
* @since 4.1
*/
public class AsyncConfigurerSupport implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return null;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -56,10 +56,20 @@ import org.springframework.core.Ordered;
* {@code spring-aspects} module JAR must be present on the classpath.
*
* <p>By default, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor
* SimpleAsyncTaskExecutor} will be used to process async method invocations. To
* customize this behavior, implement {@link AsyncConfigurer} and
* provide your own {@link java.util.concurrent.Executor Executor} through the
* {@link AsyncConfigurer#getAsyncExecutor() getExecutor()} method.
* SimpleAsyncTaskExecutor} will be used to process async method invocations. Besides,
* annotated methods having a {@code void} return type cannot transmit any exception
* back to the caller. By default, such uncaught exceptions are only logged.
*
* <p>To customize all this, implement {@link AsyncConfigurer} and
* provide:
* <ul>
* <li>your own {@link java.util.concurrent.Executor Executor} through the
* {@link AsyncConfigurer#getAsyncExecutor() getAsyncExecutor()} method, and</li>
* <li>your own {@link org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
* AsyncUncaughtExceptionHandler} through the {@link AsyncConfigurer#getAsyncUncaughtExceptionHandler()
* getAsyncUncaughtExceptionHandler()}
* method.</li>
* </ul>
*
* <pre class="code">
* &#064;Configuration
@@ -81,19 +91,29 @@ import org.springframework.core.Ordered;
* executor.initialize();
* return executor;
* }
*
* &#064;Override
* public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
* return MyAsyncUncaughtExceptionHandler();
* }
* }</pre>
*
* <p>If only one item needs to be customized, {@code null} can be returned to
* keep the default settings. Consider also extending from {@link AsyncConfigurerSupport}
* when possible.
*
* <p>For reference, the example above can be compared to the following Spring XML
* configuration:
* <pre class="code">
* {@code
* <beans>
* <task:annotation-driven executor="myExecutor"/>
* <task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/>
* <task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/>
* <bean id="asyncBean" class="com.foo.MyAsyncBean"/>
* <bean id="exceptionHandler" class="com.foo.MyAsyncUncaughtExceptionHandler"/>
* </beans>
* }</pre>
* the examples are equivalent save the setting of the <em>thread name prefix</em> of the
* the examples are equivalent except the setting of the <em>thread name prefix</em> of the
* Executor; this is because the the {@code task:} namespace {@code executor} element does
* not expose such an attribute. This demonstrates how the code-based approach allows for
* maximum configurability through direct access to actual componentry.
@@ -105,6 +125,7 @@ import org.springframework.core.Ordered;
* automatically when the bean is initialized.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
* @see Async
* @see AsyncConfigurer

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
* to enable proxy-based asynchronous method execution.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
* @see EnableAsync
* @see AsyncConfigurationSelector
@@ -50,6 +51,9 @@ public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
if (this.executor != null) {
bpp.setExecutor(this.executor);
}
if (this.exceptionHandler != null) {
bpp.setExceptionHandler(this.exceptionHandler);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -37,6 +37,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Ramnivas Laddad
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.0
*/
public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@@ -99,6 +100,10 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
if (StringUtils.hasText(executor)) {
builder.addPropertyReference("executor", executor);
}
String exceptionHandler = element.getAttribute("exception-handler");
if (StringUtils.hasText(exceptionHandler)) {
builder.addPropertyReference("exceptionHandler", exceptionHandler);
}
if (Boolean.valueOf(element.getAttribute(AopNamespaceUtils.PROXY_TARGET_CLASS_ATTRIBUTE))) {
builder.addPropertyValue("proxyTargetClass", true);
}

View File

@@ -22,7 +22,8 @@ http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframew
http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
http\://www.springframework.org/schema/task/spring-task-4.0.xsd=org/springframework/scheduling/config/spring-task-4.0.xsd
http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-4.0.xsd
http\://www.springframework.org/schema/task/spring-task-4.1.xsd=org/springframework/scheduling/config/spring-task-4.1.xsd
http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-4.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.0.xsd=org/springframework/cache/config/spring-cache-4.0.xsd

View File

@@ -0,0 +1,308 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/task"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/task"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the elements used in the Spring Framework's support for task execution and scheduling.
]]></xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.1.xsd"/>
<xsd:element name="annotation-driven">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enables the detection of @Async and @Scheduled annotations on any Spring-managed
object. If present, a proxy will be generated for executing the annotated methods
asynchronously.
See Javadoc for the org.springframework.scheduling.annotation.EnableAsync and
org.springframework.scheduling.annotation.EnableScheduling annotations for information
on code-based alternatives to this XML element.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="executor" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the java.util.Executor instance to use when invoking asynchronous methods.
If not provided, an instance of org.springframework.core.task.SimpleAsyncTaskExecutor
will be used by default.
Note that as of Spring 3.1.2, individual @Async methods may qualify which executor to
use, meaning that the executor specified here acts as a default for all non-qualified
@Async methods.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="exception-handler" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
instance to use when an exception is thrown during an asynchronous method execution
and cannot be accessed by the caller. If not provided, an instance of
org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler will be
used by default.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scheduler" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the org.springframework.scheduling.TaskScheduler or
java.util.ScheduledExecutorService instance to use when invoking scheduled
methods. If no reference is provided, a TaskScheduler backed by a single
thread scheduled executor will be used.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mode" default="proxy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Should annotated beans be proxied using Spring's AOP framework,
or should they rather be weaved with an AspectJ async execution aspect?
AspectJ weaving requires spring-aspects.jar on the classpath,
as well as load-time weaving (or compile-time weaving) enabled.
Note: The weaving-based aspect requires the @Async annotation to be
defined on the concrete class. Annotations in interfaces will not work
in that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="proxy"/>
<xsd:enumeration value="aspectj"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Are class-based (CGLIB) proxies to be created? By default, standard
Java interface-based proxies are created.
Note: Class-based proxies require the @Async annotation to be defined
on the concrete class. Annotations in interfaces will not work in
that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="scheduler">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a ThreadPoolTaskScheduler instance with configurable pool size. See Javadoc
for the org.springframework.scheduling.annotation.EnableScheduling annotation for
information on a code-based alternative to this XML element.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name for the generated ThreadPoolTaskScheduler instance.
It will also be used as the default thread name prefix.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pool-size" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The size of the ScheduledExecutorService's thread pool. The default is 1.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="executor">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a ThreadPoolTaskExecutor instance with configurable pool size,
queue-capacity, keep-alive, and rejection-policy values.
See Javadoc for the org.springframework.scheduling.annotation.EnableAsync annotation
for information on code-based alternatives to this XML element.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name for the generated ThreadPoolTaskExecutor instance.
This value will also be used as the thread name prefix which is why it is
required even when defining the executor as an inner bean: The executor
won't be directly accessible then but will nevertheless use the specified
id as the thread name prefix of the threads that it manages.
In the case of multiple task:executors, as of Spring 3.1.2 this value may be used to
qualify which executor should handle a given @Async method, e.g. @Async("executorId").
See the Javadoc for the #value attribute of Spring's @Async annotation for details.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="pool-size" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The size of the executor's thread pool as either a single value or a range
(e.g. 5-10). If no bounded queue-capacity value is provided, then a max value
has no effect unless the range is specified as 0-n. In that case, the core pool
will have a size of n, but the 'allowCoreThreadTimeout' flag will be set to true.
If a queue-capacity is provided, then the lower bound of a range will map to the
core size and the upper bound will map to the max size. If this attribute is not
provided, the default core size will be 1, and the default max size will be
Integer.MAX_VALUE (i.e. unbounded).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue-capacity" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Queue capacity for the ThreadPoolTaskExecutor. If not specified, the default will
be Integer.MAX_VALUE (i.e. unbounded).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="keep-alive" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Keep-alive time in seconds. Inactive threads that have been created beyond the
core size will timeout after the specified number of seconds elapse. If the
executor has an unbounded queue capacity and a size range represented as 0-n,
then the core threads will also be configured to timeout when inactive.
Otherwise, core threads will not ever timeout.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rejection-policy" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The RejectedExecutionHandler type. When a bounded queue cannot accept any
additional tasks, this determines the behavior. While the default is ABORT,
consider using CALLER_RUNS to throttle inbound tasks. In other words, by forcing
the caller to run the task itself, it will not be able to provide another task
until after it completes the task at hand. In the meantime, one or more tasks
may be removed from the queue. Alternatively, if it is not critical to run every
task, consider using DISCARD to drop the current task or DISCARD_OLDEST to drop
the task at the head of the queue.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="ABORT"/>
<xsd:enumeration value="CALLER_RUNS"/>
<xsd:enumeration value="DISCARD"/>
<xsd:enumeration value="DISCARD_OLDEST"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="scheduled-tasks">
<xsd:annotation>
<xsd:documentation><![CDATA[
Top-level element that contains one or more task sub-elements to be
managed by a given TaskScheduler.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="scheduled" type="scheduledTaskType" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="scheduler" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to an instance of TaskScheduler to manage the provided tasks. If not specified,
the default value will be a wrapper for a single-threaded Executor.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.scheduling.TaskScheduler"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="scheduledTaskType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Element defining a scheduled method-invoking task and its corresponding trigger.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="cron" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
A cron-based trigger. See the org.springframework.scheduling.support.CronSequenceGenerator
JavaDoc for example patterns.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="fixed-delay" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
An interval-based trigger where the interval is measured from the completion time of the
previous task. The time unit value is measured in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="fixed-rate" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
An interval-based trigger where the interval is measured from the start time of the
previous task. The time unit value is measured in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="trigger" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a bean that implements the Trigger interface.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="initial-delay" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Number of milliseconds to delay before the first execution of a 'fixed-rate' or
'fixed-delay' task.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to an object that provides a method to be invoked.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref" />
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the method to be invoked.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-method type-ref="@ref"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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,34 +16,38 @@
package org.springframework.scheduling.annotation;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
import org.junit.Test;
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.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;
/**
* @author Mark Fisher
* @author Juergen Hoeller
* @author Stephane Nicoll
*/
public class AsyncAnnotationBeanPostProcessorTests {
@Test
public void proxyCreated() {
StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ConfigurableApplicationContext context = initContext(
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
Object target = context.getBean("target");
assertTrue(AopUtils.isAopProxy(target));
context.close();
@@ -51,12 +55,8 @@ public class AsyncAnnotationBeanPostProcessorTests {
@Test
public void invokedAsynchronously() {
StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ConfigurableApplicationContext context = initContext(
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
ITestBean testBean = (ITestBean) context.getBean("target");
testBean.test();
Thread mainThread = Thread.currentThread();
@@ -68,16 +68,12 @@ public class AsyncAnnotationBeanPostProcessorTests {
@Test
public void threadNamePrefix() {
StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("testExecutor");
executor.afterPropertiesSet();
processorDefinition.getPropertyValues().add("executor", executor);
BeanDefinition targetDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
ConfigurableApplicationContext context = initContext(processorDefinition);
ITestBean testBean = (ITestBean) context.getBean("target");
testBean.test();
testBean.await(3000);
@@ -96,9 +92,86 @@ public class AsyncAnnotationBeanPostProcessorTests {
testBean.await(3000);
Thread asyncThread = testBean.getThread();
assertTrue(asyncThread.getName().startsWith("testExecutor"));
TestableAsyncUncaughtExceptionHandler exceptionHandler = (TestableAsyncUncaughtExceptionHandler)
context.getBean("exceptionHandler");
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
testBean.failWithVoid();
exceptionHandler.await(3000);
Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid");
exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class);
context.close();
}
@Test
public void handleExceptionWithFuture() {
ConfigurableApplicationContext context = initContext(
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class));
ITestBean testBean = context.getBean("target", ITestBean.class);
final Future<Object> result = testBean.failWithFuture();
try {
result.get();
}
catch (InterruptedException e) {
fail("Should not have failed with InterruptedException");
}
catch (ExecutionException e) {
// expected
assertEquals("Wrong exception cause", UnsupportedOperationException.class, e.getCause().getClass());
}
}
@Test
public void handleExceptionWithCustomExceptionHandler() {
Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid");
TestableAsyncUncaughtExceptionHandler exceptionHandler =
new TestableAsyncUncaughtExceptionHandler();
BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class);
processorDefinition.getPropertyValues().add("exceptionHandler", exceptionHandler);
ConfigurableApplicationContext context = initContext(processorDefinition);
ITestBean testBean = context.getBean("target", ITestBean.class);
assertFalse("Handler should not have been called", exceptionHandler.isCalled());
testBean.failWithVoid();
exceptionHandler.await(3000);
exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class);
}
@Test
public void exceptionHandlerThrowsUnexpectedException() {
Method m = ReflectionUtils.findMethod(TestBean.class, "failWithVoid");
TestableAsyncUncaughtExceptionHandler exceptionHandler =
new TestableAsyncUncaughtExceptionHandler(true);
BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class);
processorDefinition.getPropertyValues().add("exceptionHandler", exceptionHandler);
processorDefinition.getPropertyValues().add("executor", new DirectExecutor());
ConfigurableApplicationContext context = initContext(processorDefinition);
ITestBean testBean = context.getBean("target", ITestBean.class);
assertFalse("Handler should not have been called", exceptionHandler.isCalled());
try {
testBean.failWithVoid();
exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class);
}
catch (Exception e) {
fail("No unexpected exception should have been received");
}
}
private ConfigurableApplicationContext initContext(
BeanDefinition asyncAnnotationBeanPostProcessorDefinition) {
StaticApplicationContext context = new StaticApplicationContext();
BeanDefinition targetDefinition =
new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class);
context.registerBeanDefinition("postProcessor", asyncAnnotationBeanPostProcessorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();
return context;
}
private static interface ITestBean {
@@ -106,6 +179,10 @@ public class AsyncAnnotationBeanPostProcessorTests {
void test();
Future<Object> failWithFuture();
void failWithVoid();
void await(long timeout);
}
@@ -128,6 +205,16 @@ public class AsyncAnnotationBeanPostProcessorTests {
this.latch.countDown();
}
@Async
public Future<Object> failWithFuture() {
throw new UnsupportedOperationException("failWithFuture");
}
@Async
public void failWithVoid() {
throw new UnsupportedOperationException("failWithVoid");
}
@Override
public void await(long timeout) {
try {
@@ -139,4 +226,10 @@ public class AsyncAnnotationBeanPostProcessorTests {
}
}
private static class DirectExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -29,6 +30,7 @@ import org.junit.Test;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -38,6 +40,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.startsWith;
@@ -48,6 +51,7 @@ import static org.junit.Assert.*;
* Tests use of @EnableAsync on @Configuration classes.
*
* @author Chris Beams
* @author Stephane Nicoll
* @since 3.1
*/
public class EnableAsyncTests {
@@ -123,6 +127,11 @@ public class EnableAsyncTests {
this.threadOfExecution = Thread.currentThread();
}
@Async
public void fail() {
throw new UnsupportedOperationException();
}
public Thread getThreadOfExecution() {
return threadOfExecution;
}
@@ -233,8 +242,18 @@ public class EnableAsyncTests {
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
asyncBean.work();
Thread.sleep(500);
ctx.close();
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-"));
TestableAsyncUncaughtExceptionHandler exceptionHandler = (TestableAsyncUncaughtExceptionHandler)
ctx.getBean("exceptionHandler");
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
asyncBean.fail();
Thread.sleep(500);
Method m = ReflectionUtils.findMethod(AsyncBean.class, "fail");
exceptionHandler.assertCalledWith(m, UnsupportedOperationException.class);
ctx.close();
}
@@ -253,6 +272,16 @@ public class EnableAsyncTests {
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return exceptionHandler();
}
@Bean
public AsyncUncaughtExceptionHandler exceptionHandler() {
return new TestableAsyncUncaughtExceptionHandler();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.annotation;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
/**
* A {@link AsyncUncaughtExceptionHandler} implementation used for testing purposes.
* @author Stephane Nicoll
*/
class TestableAsyncUncaughtExceptionHandler
implements AsyncUncaughtExceptionHandler {
private final CountDownLatch latch = new CountDownLatch(1);
private UncaughtExceptionDescriptor descriptor;
private final boolean throwUnexpectedException;
TestableAsyncUncaughtExceptionHandler() {
this(false);
}
TestableAsyncUncaughtExceptionHandler(boolean throwUnexpectedException) {
this.throwUnexpectedException = throwUnexpectedException;
}
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
descriptor = new UncaughtExceptionDescriptor(ex, method);
this.latch.countDown();
if (throwUnexpectedException) {
throw new IllegalStateException("Test exception");
}
}
public boolean isCalled() {
return descriptor != null;
}
public void assertCalledWith(Method expectedMethod, Class<? extends Throwable> expectedExceptionType) {
assertNotNull("Handler not called", descriptor);
assertEquals("Wrong exception type", expectedExceptionType, descriptor.ex.getClass());
assertEquals("Wrong method", expectedMethod, descriptor.method);
}
public void await(long timeout) {
try {
this.latch.await(timeout, TimeUnit.MILLISECONDS);
}
catch (Exception e) {
Thread.currentThread().interrupt();
}
}
private static class UncaughtExceptionDescriptor {
private final Throwable ex;
private final Method method;
private UncaughtExceptionDescriptor(Throwable ex, Method method) {
this.ex = ex;
this.method = method;
}
}
}

View File

@@ -3,15 +3,15 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd">
<!--
<context:load-time-weaver aspectj-weaving="on"/>
-->
<task:annotation-driven executor="executor" scheduler="scheduler"/>
<task:annotation-driven executor="executor" exception-handler="exceptionHandler" scheduler="scheduler"/>
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="target" method="test" fixed-rate="1000"/>
@@ -21,6 +21,9 @@
<property name="threadNamePrefix" value="testExecutor"/>
</bean>
<bean id="exceptionHandler"
class="org.springframework.scheduling.annotation.TestableAsyncUncaughtExceptionHandler"/>
<bean id="scheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="threadNamePrefix" value="testScheduler"/>
</bean>

View File

@@ -44652,6 +44652,28 @@ specified with the `<qualifier>` element or Spring's `@Qualifier` annotation.
[[scheduling-annotation-support-exception]]
==== Exception management with @Async
When an `@Async` method has a `Future` typed return value, it is easy to manage
an exception that was thrown during the method execution as this exception will
be thrown when calling `get` on the `Future` result. With a void return type
however, the exception is uncaught and cannot be transmitted. For those cases, an
`AsyncUncaughtExceptionHandler` can be provided to handle such exceptions.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
----
By default, the exception is simply logged. A custom `AsyncUncaughtExceptionHandler` can
be defined _via_ `AsyncConfigurer` or the `task:annotation-driven` XML element.
[[scheduling-task-namespace]]
=== The Task Namespace