added mode="proxy"/"aspectj" and proxy-target-class options to task namespace; switched to concise names for async aspects

This commit is contained in:
Juergen Hoeller 2010-10-15 20:50:23 +00:00
parent 171f1ee097
commit 8c9b64c948
10 changed files with 324 additions and 218 deletions

View File

@ -28,22 +28,18 @@ import org.springframework.core.task.support.TaskExecutorAdapter;
/**
* Abstract aspect that routes selected methods asynchronously.
*
* <p>This aspect, by default, uses {@link SimpleAsyncTaskExecutor} to route
* method execution. However, you may inject it with any implementation of
* {@link Executor} to override the default.
* <p>This aspect needs to be injected with an implementation of
* {@link Executor} to activate it for a specific thread pool.
* Otherwise it will simply delegate all calls synchronously.
*
* @author Ramnivas Laddad
* @author Juergen Hoeller
* @since 3.0.5
*/
public abstract aspect AbstractAsynchronousExecutionAspect {
public abstract aspect AbstractAsyncExecutionAspect {
private AsyncTaskExecutor asyncExecutor;
public AbstractAsynchronousExecutionAspect() {
// Set default executor, which may be replaced by calling setExecutor.
setExecutor(new SimpleAsyncTaskExecutor());
}
public void setExecutor(Executor executor) {
if (executor instanceof AsyncTaskExecutor) {
this.asyncExecutor = (AsyncTaskExecutor) executor;
@ -54,6 +50,9 @@ public abstract aspect AbstractAsynchronousExecutionAspect {
}
Object around() : asyncMethod() {
if (this.asyncExecutor == null) {
return proceed();
}
Callable<Object> callable = new Callable<Object>() {
public Object call() throws Exception {
Object result = proceed();
@ -62,7 +61,7 @@ public abstract aspect AbstractAsynchronousExecutionAspect {
}
return null;
}};
Future<?> result = asyncExecutor.submit(callable);
Future<?> result = this.asyncExecutor.submit(callable);
if (Future.class.isAssignableFrom(((MethodSignature) thisJoinPointStaticPart.getSignature()).getReturnType())) {
return result;
}

View File

@ -33,7 +33,7 @@ import org.springframework.scheduling.annotation.Async;
* @author Ramnivas Laddad
* @since 3.0.5
*/
public aspect AnnotationDrivenAsynchronousExecutionAspect extends AbstractAsynchronousExecutionAspect {
public aspect AnnotationAsyncExecutionAspect extends AbstractAsyncExecutionAspect {
private pointcut asyncMarkedMethod()
: execution(@Async (void || Future+) *(..));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2007 the original author or authors.
* Copyright 2002-2010 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 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,8 +20,7 @@ import org.springframework.transaction.annotation.AnnotationTransactionAttribute
import org.springframework.transaction.annotation.Transactional;
/**
* Concrete AspectJ transaction aspect using Spring Transactional annotation
* for JDK 1.5+.
* Concrete AspectJ transaction aspect using Spring's @Transactional annotation.
*
* <p>When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that

View File

@ -11,6 +11,7 @@
<aspects>
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
<aspect name="org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"/>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
</aspects>

View File

@ -33,7 +33,7 @@ import org.springframework.scheduling.annotation.AsyncResult;
/**
* @author Ramnivas Laddad
*/
public class AnnotationDrivenAsynchronousExecutionAspectTests {
public class AnnotationAsyncExecutionAspectTests {
private static final long WAIT_TIME = 1000; //milli seconds
@ -42,7 +42,7 @@ public class AnnotationDrivenAsynchronousExecutionAspectTests {
@Before
public void setUp() {
executor = new CountingExecutor();
AnnotationDrivenAsynchronousExecutionAspect.aspectOf().setExecutor(executor);
AnnotationAsyncExecutionAspect.aspectOf().setExecutor(executor);
}
@Test
@ -150,7 +150,7 @@ public class AnnotationDrivenAsynchronousExecutionAspectTests {
// We need to keep this commented out, otherwise there will be a compile-time error.
// Please uncomment and re-comment this periodically to check that the compiler
// produces an error message due to the 'declare error' statement
// in AnnotationDrivenAsynchronousExecutionAspect
// in AnnotationAsyncExecutionAspect
// @Async public int getInt() {
// return 0;
// }
@ -164,7 +164,7 @@ public class AnnotationDrivenAsynchronousExecutionAspectTests {
counter++;
}
// Manually check that there is a warning from the 'declare warning' statement in AnnotationDrivenAsynchronousExecutionAspect
// Manually check that there is a warning from the 'declare warning' statement in AnnotationAsynchExecutionAspect
public int return5() {
return 5;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.springframework.scheduling.config;
import org.w3c.dom.Element;
import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@ -32,16 +33,26 @@ import org.springframework.util.StringUtils;
* Parser for the 'annotation-driven' element of the 'task' namespace.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @since 3.0
*/
public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
/**
* The bean name of the internally managed async annotation processor.
* The bean name of the internally managed async annotation processor (mode="proxy").
*/
public static final String ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME =
"org.springframework.scheduling.annotation.internalAsyncAnnotationProcessor";
/**
* The bean name of the internally managed transaction aspect (mode="aspectj").
*/
public static final String ASYNC_EXECUTION_ASPECT_BEAN_NAME =
"org.springframework.transaction.config.internalTransactionAspect";
private static final String ASYNC_EXECUTION_ASPECT_CLASS_NAME =
"org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect";
/**
* The bean name of the internally managed scheduled annotation processor.
*/
@ -58,6 +69,14 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
// Nest the concrete post-processor bean in the surrounding component.
BeanDefinitionRegistry registry = parserContext.getRegistry();
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerAsyncExecutionAspect(element, parserContext);
}
else {
// mode="proxy"
if (registry.containsBeanDefinition(ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)) {
parserContext.getReaderContext().error(
"Only one AsyncAnnotationBeanPostProcessor may exist within the context.", source);
@ -70,8 +89,13 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
if (StringUtils.hasText(executor)) {
builder.addPropertyReference("executor", executor);
}
if (Boolean.valueOf(element.getAttribute(AopNamespaceUtils.PROXY_TARGET_CLASS_ATTRIBUTE))) {
builder.addPropertyValue("proxyTargetClass", true);
}
registerPostProcessor(parserContext, builder, ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME);
}
}
if (registry.containsBeanDefinition(SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
parserContext.getReaderContext().error(
"Only one ScheduledAnnotationBeanPostProcessor may exist within the context.", source);
@ -93,6 +117,20 @@ public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParse
return null;
}
private void registerAsyncExecutionAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(ASYNC_EXECUTION_ASPECT_BEAN_NAME)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
ASYNC_EXECUTION_ASPECT_CLASS_NAME);
builder.setFactoryMethod("aspectOf");
String executor = element.getAttribute("executor");
if (StringUtils.hasText(executor)) {
builder.addPropertyReference("executor", executor);
}
parserContext.registerBeanComponent(
new BeanComponentDefinition(builder.getBeanDefinition(), ASYNC_EXECUTION_ASPECT_BEAN_NAME));
}
}
private static void registerPostProcessor(
ParserContext parserContext, BeanDefinitionBuilder builder, String beanName) {

View File

@ -44,6 +44,39 @@
]]></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>

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 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,22 +16,23 @@
package org.springframework.scheduling.annotation;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
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.support.GenericXmlApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Mark Fisher
* @author Juergen Hoeller
*/
public class AsyncAnnotationBeanPostProcessorTests {
@ -85,6 +86,19 @@ public class AsyncAnnotationBeanPostProcessorTests {
context.close();
}
@Test
public void configuredThroughNamespace() {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.load(new ClassPathResource("taskNamespaceTests.xml", getClass()));
context.refresh();
ITestBean testBean = (ITestBean) context.getBean("target");
testBean.test();
testBean.await(3000);
Thread asyncThread = testBean.getThread();
assertTrue(asyncThread.getName().startsWith("testExecutor"));
context.close();
}
private static interface ITestBean {
@ -96,7 +110,7 @@ public class AsyncAnnotationBeanPostProcessorTests {
}
private static class TestBean implements ITestBean {
public static class TestBean implements ITestBean {
private Thread thread;

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--
<context:load-time-weaver aspectj-weaving="on"/>
-->
<task:annotation-driven executor="executor"/>
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="threadNamePrefix" value="testExecutor"/>
</bean>
<bean id="target" class="org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessorTests$TestBean"/>
</beans>