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:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
* @Configuration
|
||||
@@ -81,19 +91,29 @@ import org.springframework.core.Ordered;
|
||||
* executor.initialize();
|
||||
* return executor;
|
||||
* }
|
||||
*
|
||||
* @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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user