From 3e9e9e2267cc64a23d2672c0e5b2d5d7237b7e5f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 10 Feb 2009 11:24:05 +0000 Subject: [PATCH] added @Async annotation, AsyncExecutionInterceptor, AsyncAnnotationAdvisor git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@613 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../resources/changelog.txt | 30 +- .../AsyncExecutionInterceptor.java | 103 +++++++ .../scheduling/annotation/Async.java | 49 ++++ .../annotation/AsyncAnnotationAdvisor.java | 149 ++++++++++ .../scheduling/annotation/AsyncResult.java | 63 +++++ .../scheduling/annotation/package.html | 7 + .../AutowiredQualifierFooService.java | 9 +- .../java/example/scannable/FooService.java | 11 +- .../example/scannable/FooServiceImpl.java | 12 +- .../scannable/ScopedProxyTestBean.java | 9 +- .../scannable/ServiceInvocationCounter.java | 9 + .../ClassPathBeanDefinitionScannerTests.java | 38 ++- .../context/annotation/SimpleConfigTests.java | 32 ++- .../context/annotation/simpleConfigTests.xml | 18 +- .../annotation/AsyncExecutionTests.java | 261 ++++++++++++++++++ 15 files changed, 752 insertions(+), 48 deletions(-) create mode 100644 org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java create mode 100644 org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Async.java create mode 100644 org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java create mode 100644 org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java create mode 100644 org.springframework.context/src/main/java/org/springframework/scheduling/annotation/package.html create mode 100644 org.springframework.context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index de312de1f14..fc20bd0b945 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -3,15 +3,36 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org -Changes in version 3.0.0.M2 (2009-01-23) +Changes in version 3.0.0.M2 (2009-02-11) ---------------------------------------- +* "systemProperties" bean is not considered a default match for type Properties anymore +* registered plain singletons will be fully matched according to their qualifiers +* all "taskExecutor" bean properties now accept any "java.util.concurrent.Executor" +* added "Future submit(Runnable)" and "Future submit(Callable)" to AsyncTaskExecutor +* SimpleAsyncTaskExecutor supports a custom "java.util.concurrent.ThreadFactory" +* SchedulingTaskExecutor interface extends AsyncTaskExecutor now +* added ThreadPoolExecutorFactoryBean (exposing the native ExecutorService interface) +* added ExecutorServiceAdapter class as a standard wrapper for a Spring TaskExecutor +* reduced backport-concurrent support to TaskExecutor adapters +* added @Async annotation and AsyncAnnotationAdvisor (namespace support coming in M3) +* EJB 3.1's @Asynchronous annotation gets detected and supported by default as well +* ApplicationListener beans get obtained on demand, supporting non-singletons as well +* ApplicationListeners will be called in the order according to the Ordered contract +* generified ApplicationListener interface, narrowing the event type to be received +* generified Hibernate/Jdo/JpaCallback with generic "doInXxx" return type +* HibernateOperations uses generic parameter/return types where possible +* JdoOperations uses generic parameter/return types where possible (following JDO 2.1) +* removed "flush" operation from JdoDialect (fully relying on JDO 2.0+ compliance now) +* added JDO 2.1 compliant StandardPersistenceManagerProxy/SpringPersistenceManagerProxy +* Spring-created EntityManagers support JPA 2.0 draft API ("unwrap", "getQueryBuilder") +* Spring initiates JPA 2.0 query timeout with remaining Spring transaction timeout +* added support for WebSphere's ResourceAdapter-managed messaging transactions * introduced OXM support package (originating from Spring Web Services) * introduced OXM-based MarshallingMessageConverter for JMS * introduced OXM-based MarshallingView for Spring MVC -* refined @PathVariable handling -* updated Spring Portlet mocks for Portlet API 2.0 -* updated Spring Portlet MVC infrastructure for Portlet API 2.0 +* refined @PathVariable handling in MVC handler methods +* updated Spring Portlet MVC infrastructure and mocks for Portlet API 2.0 * added resource and event methods to Portlet HandlerAdapter/HandlerInterceptor * added resolveException method for resource requests to HandlerExceptionResolver * introduced Resource/EventAwareController subinterfaces of Portlet Controller @@ -34,6 +55,7 @@ Changes in version 3.0.0.M1 (2008-12-05) * removed ContextLoaderServlet and Log4jConfigServlet * deprecated form controller hierarchy in favor of @MVC form object handling * deprecated JUnit 3.8 test class hierarchy in favor of test context framework +* revised TaskExecutor interface to extend "java.util.concurrent.Executor" now * introduced Spring EL parser in org.springframework.expression package * introduced #{...} expression support in bean definitions * introduced @Value annotation for embedded expression support diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java new file mode 100644 index 00000000000..e64de27c6c0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -0,0 +1,103 @@ +/* + * Copyright 2002-2009 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.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.core.Ordered; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.support.TaskExecutorAdapter; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * AOP Alliance MethodInterceptor that processes method invocations + * asynchronously, using a given {@link org.springframework.core.task.AsyncTaskExecutor}. + * Typically used with the {@link org.springframework.context.task.Async} annotation. + * + *

In terms of target method signatures, any parameter types are supported. + * However, the return type is constrained to either void or + * java.util.concurrent.Future. In the latter case, the Future handle + * returned from the proxy will be an actual asynchronous Future that can be used + * to track the result of the asynchronous method execution. However, since the + * target method needs to implement the same signature, it will have to return + * a temporary Future handle that just passes the return value through + * (like Spring's {@link org.springframework.scheduling.annotation.AsyncResult} + * or EJB 3.1's javax.ejb.AsyncResult). + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.scheduling.annotation.Async + * @see org.springframework.scheduling.annotation.AsyncAnnotationAdvisor + */ +public class AsyncExecutionInterceptor implements MethodInterceptor, Ordered { + + private final AsyncTaskExecutor asyncExecutor; + + + /** + * Create a new AsyncExecutionInterceptor. + * @param asyncExecutor the Spring AsyncTaskExecutor to delegate to + */ + public AsyncExecutionInterceptor(AsyncTaskExecutor asyncExecutor) { + Assert.notNull(asyncExecutor, "TaskExecutor must not be null"); + this.asyncExecutor = asyncExecutor; + } + + /** + * Create a new AsyncExecutionInterceptor. + * @param asyncExecutor the java.util.concurrent Executor + * to delegate to (typically a {@link java.util.concurrent.ExecutorService} + */ + public AsyncExecutionInterceptor(Executor asyncExecutor) { + this.asyncExecutor = new TaskExecutorAdapter(asyncExecutor); + } + + + public Object invoke(final MethodInvocation invocation) throws Throwable { + Future result = this.asyncExecutor.submit(new Callable() { + public Object call() throws Exception { + try { + Object result = invocation.proceed(); + if (result instanceof Future) { + return ((Future) result).get(); + } + } + catch (Throwable ex) { + ReflectionUtils.rethrowException(ex); + } + return null; + } + }); + if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) { + return result; + } + else { + return null; + } + } + + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Async.java b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Async.java new file mode 100644 index 00000000000..90276bf6386 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/Async.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2009 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that marks a method as a candidate for asynchronous execution. + * Can also be used at the type level, in which case all of the type's methods are + * considered as asynchronous. + * + *

In terms of target method signatures, any parameter types are supported. + * However, the return type is constrained to either void or + * java.util.concurrent.Future. In the latter case, the Future handle + * returned from the proxy will be an actual asynchronous Future that can be used + * to track the result of the asynchronous method execution. However, since the + * target method needs to implement the same signature, it will have to return + * a temporary Future handle that just passes the return value through: e.g. + * Spring's {@link AsyncResult} or EJB 3.1's javax.ejb.AsyncResult. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.aop.interceptor.AsyncExecutionInterceptor + * @see AsyncAnnotationAdvisor + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Async { + +} diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java new file mode 100644 index 00000000000..d1d38311ab2 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2009 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.lang.annotation.Annotation; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.Executor; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.interceptor.AsyncExecutionInterceptor; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.ComposablePointcut; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Advisor that activates asynchronous method execution through the {@link Async} + * annotation. This annotation can be used at the method and type level in + * implementation classes as well as in service interfaces. + * + *

This advisor detects the EJB 3.1 javax.ejb.Asynchronous + * annotation as well, treating it exactly like Spring's own Async. + * Furthermore, a custom async annotation type may get specified through the + * {@link #setAsyncAnnotationType "asyncAnnotationType"} property. + * + * @author Juergen Hoeller + * @since 3.0 + * @see PersistenceExceptionTranslationAdvisor + * @see org.springframework.stereotype.Repository + * @see org.springframework.dao.DataAccessException + * @see org.springframework.dao.support.PersistenceExceptionTranslator + */ +public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor { + + private Advice advice; + + private Pointcut pointcut; + + + /** + * Create a new ConcurrencyAnnotationBeanPostProcessor for bean-style configuration. + */ + public AsyncAnnotationAdvisor() { + this(new SimpleAsyncTaskExecutor()); + } + + /** + * Create a new ConcurrencyAnnotationBeanPostProcessor for the given task executor. + * @param executor the task executor to use for asynchronous methods + */ + @SuppressWarnings("unchecked") + public AsyncAnnotationAdvisor(Executor executor) { + Set> asyncAnnotationTypes = new LinkedHashSet>(2); + asyncAnnotationTypes.add(Async.class); + try { + asyncAnnotationTypes.add(ClassUtils.forName( + "javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader())); + } + catch (ClassNotFoundException ex) { + // If EJB 3.1 API not present, simply ignore. + } + this.advice = buildAdvice(executor); + this.pointcut = buildPointcut(asyncAnnotationTypes); + } + + /** + * Specify the task executor to use for asynchronous methods. + */ + public void setTaskExecutor(Executor executor) { + this.advice = buildAdvice(executor); + } + + /** + * Set the 'async' annotation type. + *

The default async annotation type is the {@link Async} annotation, as well + * as the EJB 3.1 javax.ejb.Asynchronous annotation (if present). + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a method is to + * be executed asynchronously. + * @param asyncAnnotationType the desired annotation type + */ + public void setAsyncAnnotationType(Class asyncAnnotationType) { + Assert.notNull(asyncAnnotationType, "'asyncAnnotationType' must not be null"); + Set> asyncAnnotationTypes = new HashSet>(); + asyncAnnotationTypes.add(asyncAnnotationType); + this.pointcut = buildPointcut(asyncAnnotationTypes); + } + + + public Advice getAdvice() { + return this.advice; + } + + public Pointcut getPointcut() { + return this.pointcut; + } + + + protected Advice buildAdvice(Executor executor) { + if (executor instanceof AsyncTaskExecutor) { + return new AsyncExecutionInterceptor((AsyncTaskExecutor) executor); + } + else { + return new AsyncExecutionInterceptor(executor); + } + } + + /** + * Calculate a pointcut for the given target class, if any. + * @param targetClass the class to introspect + * @return the applicable Pointcut object, or null if none + */ + protected Pointcut buildPointcut(Set> asyncAnnotationTypes) { + ComposablePointcut result = null; + for (Class asyncAnnotationType : asyncAnnotationTypes) { + Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true); + Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType); + if (result == null) { + result = new ComposablePointcut(cpc).union(mpc); + } + else { + result.union(cpc).union(mpc); + } + } + return result; + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java new file mode 100644 index 00000000000..f13e17cea3c --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2009 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.Future; +import java.util.concurrent.TimeUnit; + +/** + * A pass-through Future handle that can be used for method signatures + * which are declared with a Future return type for asynchronous execution. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.scheduling.annotation.Async + */ +public class AsyncResult implements Future { + + private final V value; + + + /** + * Create a new AsyncResult holder. + * @param value the value to pass through + */ + public AsyncResult(V value) { + this.value = value; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + public boolean isCancelled() { + return false; + } + + public boolean isDone() { + return true; + } + + public V get() { + return this.value; + } + + public V get(long timeout, TimeUnit unit) { + return this.value; + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/package.html b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/package.html new file mode 100644 index 00000000000..2a158bb5f62 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/annotation/package.html @@ -0,0 +1,7 @@ + + + +JDK 1.5+ annotation for asynchronous method execution. + + + diff --git a/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java b/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java index 6db0d795543..1fcc0e6d429 100644 --- a/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java +++ b/org.springframework.context/src/test/java/example/scannable/AutowiredQualifierFooService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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,13 +16,16 @@ package example.scannable; +import java.util.concurrent.Future; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.AsyncResult; /** * @author Mark Fisher + * @author Juergen Hoeller */ public class AutowiredQualifierFooService implements FooService { @@ -44,6 +47,10 @@ public class AutowiredQualifierFooService implements FooService { return this.fooDao.findFoo(id); } + public Future asyncFoo(int id) { + return new AsyncResult(this.fooDao.findFoo(id)); + } + public boolean isInitCalled() { return this.initCalled; } diff --git a/org.springframework.context/src/test/java/example/scannable/FooService.java b/org.springframework.context/src/test/java/example/scannable/FooService.java index f5fc4e6bfb0..df48735445f 100644 --- a/org.springframework.context/src/test/java/example/scannable/FooService.java +++ b/org.springframework.context/src/test/java/example/scannable/FooService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package example.scannable; +import java.util.concurrent.Future; + +import org.springframework.scheduling.annotation.Async; + /** * @author Mark Fisher * @author Juergen Hoeller @@ -23,7 +27,10 @@ package example.scannable; public interface FooService { String foo(int id); - + + @Async + Future asyncFoo(int id); + boolean isInitCalled(); } diff --git a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java index 1b13d9644db..03884b65c63 100644 --- a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java +++ b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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. @@ -17,7 +17,7 @@ package example.scannable; import java.util.List; - +import java.util.concurrent.Future; import javax.annotation.PostConstruct; import org.springframework.beans.factory.BeanFactory; @@ -30,7 +30,9 @@ import org.springframework.context.MessageSource; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; /** * @author Mark Fisher @@ -73,6 +75,12 @@ public class FooServiceImpl implements FooService { return this.fooDao.findFoo(id); } + public Future asyncFoo(int id) { + System.out.println(Thread.currentThread().getName()); + Assert.state(ServiceInvocationCounter.getThreadLocalCount() != null, "Thread-local counter not exposed"); + return new AsyncResult(this.fooDao.findFoo(id)); + } + public boolean isInitCalled() { return this.initCalled; } diff --git a/org.springframework.context/src/test/java/example/scannable/ScopedProxyTestBean.java b/org.springframework.context/src/test/java/example/scannable/ScopedProxyTestBean.java index 5168b2b66ba..34d31600986 100644 --- a/org.springframework.context/src/test/java/example/scannable/ScopedProxyTestBean.java +++ b/org.springframework.context/src/test/java/example/scannable/ScopedProxyTestBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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,7 +16,10 @@ package example.scannable; +import java.util.concurrent.Future; + import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.annotation.AsyncResult; /** * @author Mark Fisher @@ -29,6 +32,10 @@ public class ScopedProxyTestBean implements FooService { return "bar"; } + public Future asyncFoo(int id) { + return new AsyncResult("bar"); + } + public boolean isInitCalled() { return false; } diff --git a/org.springframework.context/src/test/java/example/scannable/ServiceInvocationCounter.java b/org.springframework.context/src/test/java/example/scannable/ServiceInvocationCounter.java index a0aa9da7223..257a2d8bc52 100644 --- a/org.springframework.context/src/test/java/example/scannable/ServiceInvocationCounter.java +++ b/org.springframework.context/src/test/java/example/scannable/ServiceInvocationCounter.java @@ -31,16 +31,25 @@ public class ServiceInvocationCounter { private int useCount; + private static final ThreadLocal threadLocalCount = new ThreadLocal(); + + @Pointcut("execution(* example.scannable.FooService+.*(..))") public void serviceExecution() {} @Before("serviceExecution()") public void countUse() { this.useCount++; + this.threadLocalCount.set(this.useCount); + System.out.println(""); } public int getCount() { return this.useCount; } + public static Integer getThreadLocalCount() { + return threadLocalCount.get(); + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java index 745fe8b3e49..72abf04abb0 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathBeanDefinitionScannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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,9 +16,13 @@ package org.springframework.context.annotation; -import static org.junit.Assert.*; - +import example.scannable.CustomComponent; +import example.scannable.FooService; +import example.scannable.FooServiceImpl; +import example.scannable.NamedStubDao; +import example.scannable.StubFooDao; import org.aspectj.lang.annotation.Aspect; +import static org.junit.Assert.*; import org.junit.Test; import org.springframework.beans.TestBean; @@ -35,12 +39,6 @@ import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.stereotype.Component; -import example.scannable.CustomComponent; -import example.scannable.FooService; -import example.scannable.FooServiceImpl; -import example.scannable.NamedStubDao; -import example.scannable.StubFooDao; - /** * @author Mark Fisher * @author Juergen Hoeller @@ -64,8 +62,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean("thoreau")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -119,8 +117,8 @@ public class ClassPathBeanDefinitionScannerTests { } catch (IllegalStateException ex) { // expected - assertTrue(ex.getMessage().indexOf("stubFooDao") != -1); - assertTrue(ex.getMessage().indexOf(StubFooDao.class.getName()) != -1); + assertTrue(ex.getMessage().contains("stubFooDao")); + assertTrue(ex.getMessage().contains(StubFooDao.class.getName())); } } @@ -194,8 +192,8 @@ public class ClassPathBeanDefinitionScannerTests { assertEquals(4, beanCount); assertTrue(context.containsBean("messageBean")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -212,8 +210,8 @@ public class ClassPathBeanDefinitionScannerTests { assertFalse(context.containsBean("myNamedComponent")); assertFalse(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -230,8 +228,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -247,8 +245,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -264,8 +262,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -282,8 +280,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertFalse(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertFalse(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertFalse(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertFalse(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -300,8 +298,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test @@ -318,8 +316,8 @@ public class ClassPathBeanDefinitionScannerTests { assertTrue(context.containsBean("myNamedComponent")); assertTrue(context.containsBean("myNamedDao")); assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); - assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); + assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } @Test diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleConfigTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleConfigTests.java index 2b31f74f61e..fc186b8e434 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleConfigTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 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,38 +16,44 @@ package org.springframework.context.annotation; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.springframework.context.support.ClassPathXmlApplicationContext; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import example.scannable.FooService; import example.scannable.ServiceInvocationCounter; +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author Mark Fisher + * @author Juergen Hoeller */ public class SimpleConfigTests { @Test public void testFooService() throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(getConfigLocations(), getClass()); - - FooService fooService = (FooService) ctx.getBean("fooServiceImpl"); - ServiceInvocationCounter serviceInvocationCounter = (ServiceInvocationCounter) ctx.getBean("serviceInvocationCounter"); + + FooService fooService = ctx.getBean("fooServiceImpl", FooService.class); + ServiceInvocationCounter serviceInvocationCounter = ctx.getBean("serviceInvocationCounter", ServiceInvocationCounter.class); String value = fooService.foo(1); assertEquals("bar", value); - - assertEquals(1, serviceInvocationCounter.getCount()); - - fooService.foo(1); + + Future future = fooService.asyncFoo(1); + assertTrue(future instanceof FutureTask); + assertEquals("bar", future.get()); + assertEquals(2, serviceInvocationCounter.getCount()); + + fooService.foo(1); + assertEquals(3, serviceInvocationCounter.getCount()); } public String[] getConfigLocations() { return new String[] {"simpleConfigTests.xml"}; } - } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/simpleConfigTests.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/simpleConfigTests.xml index d3916174f18..4b68500000b 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/annotation/simpleConfigTests.xml +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/simpleConfigTests.xml @@ -8,13 +8,21 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> - + - + + + + + - + - + - + diff --git a/org.springframework.context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/org.springframework.context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java new file mode 100644 index 00000000000..9dbc91f7e9f --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -0,0 +1,261 @@ +/* + * Copyright 2002-2009 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.Future; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.scheduling.annotation.AsyncResult; + +/** + * @author Juergen Hoeller + */ +public class AsyncExecutionTests { + + private static String originalThreadName; + + private static int listenerCalled = 0; + + private static int listenerConstructed = 0; + + + @Test + public void asyncMethods() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + AsyncMethodBean asyncTest = context.getBean("asyncTest", AsyncMethodBean.class); + asyncTest.doNothing(5); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + + @Test + public void asyncClass() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + AsyncClassBean asyncTest = context.getBean("asyncTest", AsyncClassBean.class); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + + @Test + public void asyncInterface() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncInterfaceBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + AsyncInterface asyncTest = context.getBean("asyncTest", AsyncInterface.class); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + + @Test + public void asyncMethodsInInterface() throws Exception { + originalThreadName = Thread.currentThread().getName(); + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodsInterfaceBean.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + AsyncMethodsInterface asyncTest = context.getBean("asyncTest", AsyncMethodsInterface.class); + asyncTest.doNothing(5); + asyncTest.doSomething(10); + Future future = asyncTest.returnSomething(20); + assertEquals("20", future.get()); + } + + @Test + public void asyncMethodListener() throws Exception { + originalThreadName = Thread.currentThread().getName(); + listenerCalled = 0; + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodListener.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + Thread.sleep(1000); + assertEquals(1, listenerCalled); + } + + @Test + public void asyncClassListener() throws Exception { + originalThreadName = Thread.currentThread().getName(); + listenerCalled = 0; + listenerConstructed = 0; + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassListener.class)); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + context.close(); + Thread.sleep(1000); + assertEquals(2, listenerCalled); + assertEquals(1, listenerConstructed); + } + + @Test + public void asyncPrototypeClassListener() throws Exception { + originalThreadName = Thread.currentThread().getName(); + listenerCalled = 0; + listenerConstructed = 0; + GenericApplicationContext context = new GenericApplicationContext(); + RootBeanDefinition listenerDef = new RootBeanDefinition(AsyncClassListener.class); + listenerDef.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + context.registerBeanDefinition("asyncTest", listenerDef); + context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class)); + context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class)); + context.refresh(); + context.close(); + Thread.sleep(1000); + assertEquals(2, listenerCalled); + assertEquals(2, listenerConstructed); + } + + + public static class AsyncMethodBean { + + public void doNothing(int i) { + assertTrue(Thread.currentThread().getName().equals(originalThreadName)); + } + + @Async + public void doSomething(int i) { + System.out.println(Thread.currentThread().getName() + ": " + i); + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + + @Async + public Future returnSomething(int i) { + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + return new AsyncResult(Integer.toString(i)); + } + } + + + @Async + public static class AsyncClassBean { + + public void doSomething(int i) { + System.out.println(Thread.currentThread().getName() + ": " + i); + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + + public Future returnSomething(int i) { + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + return new AsyncResult(Integer.toString(i)); + } + } + + + @Async + public interface AsyncInterface { + + void doSomething(int i); + + Future returnSomething(int i); + } + + + public static class AsyncInterfaceBean implements AsyncInterface { + + public void doSomething(int i) { + System.out.println(Thread.currentThread().getName() + ": " + i); + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + + public Future returnSomething(int i) { + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + return new AsyncResult(Integer.toString(i)); + } + } + + + public interface AsyncMethodsInterface { + + void doNothing(int i); + + @Async + void doSomething(int i); + + @Async + Future returnSomething(int i); + } + + + public static class AsyncMethodsInterfaceBean implements AsyncMethodsInterface { + + public void doNothing(int i) { + assertTrue(Thread.currentThread().getName().equals(originalThreadName)); + } + + public void doSomething(int i) { + System.out.println(Thread.currentThread().getName() + ": " + i); + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + + public Future returnSomething(int i) { + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + return new AsyncResult(Integer.toString(i)); + } + } + + + public static class AsyncMethodListener implements ApplicationListener { + + @Async + public void onApplicationEvent(ApplicationEvent event) { + listenerCalled++; + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + } + + + @Async + public static class AsyncClassListener implements ApplicationListener { + + public AsyncClassListener() { + listenerConstructed++; + } + + public void onApplicationEvent(ApplicationEvent event) { + listenerCalled++; + assertTrue(!Thread.currentThread().getName().equals(originalThreadName)); + } + } + +}