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 extends Annotation> 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 extends Annotation> 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));
+ }
+ }
+
+}