diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index 134bc2f3e02..8dce6323299 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,9 +31,12 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.support.TaskExecutorAdapter; import org.springframework.lang.UsesJava8; import org.springframework.util.ClassUtils; @@ -58,6 +61,15 @@ import org.springframework.util.concurrent.ListenableFuture; */ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { + /** + * The default name of the {@link TaskExecutor} bean to pick up: "taskExecutor". + *

Note that the initial lookup happens by type; this is just the fallback + * in case of multiple executor beans found in the context. + * @since 4.2.6 + */ + public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor"; + + // Java 8's CompletableFuture type present? private static final boolean completableFuturePresent = ClassUtils.isPresent( "java.util.concurrent.CompletableFuture", AsyncExecutionInterceptor.class.getClassLoader()); @@ -67,7 +79,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { private final Map executors = new ConcurrentHashMap(16); - private Executor defaultExecutor; + private volatile Executor defaultExecutor; private AsyncUncaughtExceptionHandler exceptionHandler; @@ -75,33 +87,37 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { /** - * Create a new {@link AsyncExecutionAspectSupport}, using the provided default - * executor unless individual async methods indicate via qualifier that a more - * specific executor should be used. - * @param defaultExecutor the executor to use when executing asynchronous methods - * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use + * Create a new instance with a default {@link AsyncUncaughtExceptionHandler}. + * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor} + * or {@link java.util.concurrent.ExecutorService}) to delegate to, unless a more specific + * executor has been requested via a qualifier on the async method, in which case the + * executor will be looked up at invocation time against the enclosing bean factory */ - public AsyncExecutionAspectSupport(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { - this.defaultExecutor = defaultExecutor; - this.exceptionHandler = exceptionHandler; + public AsyncExecutionAspectSupport(Executor defaultExecutor) { + this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler()); } /** - * Create a new instance with a default {@link AsyncUncaughtExceptionHandler}. + * Create a new {@link AsyncExecutionAspectSupport} with the given exception handler. + * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor} + * or {@link java.util.concurrent.ExecutorService}) to delegate to, unless a more specific + * executor has been requested via a qualifier on the async method, in which case the + * executor will be looked up at invocation time against the enclosing bean factory + * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use */ - public AsyncExecutionAspectSupport(Executor defaultExecutor) { - this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler()); + public AsyncExecutionAspectSupport(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { + this.defaultExecutor = defaultExecutor; + this.exceptionHandler = exceptionHandler; } /** * Supply the executor to be used when executing async methods. - * @param defaultExecutor the {@code Executor} (typically a Spring {@code - * AsyncTaskExecutor} or {@link java.util.concurrent.ExecutorService}) to delegate to - * unless a more specific executor has been requested via a qualifier on the async - * method, in which case the executor will be looked up at invocation time against the - * enclosing bean factory. - * @see #getExecutorQualifier + * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor} + * or {@link java.util.concurrent.ExecutorService}) to delegate to, unless a more specific + * executor has been requested via a qualifier on the async method, in which case the + * executor will be looked up at invocation time against the enclosing bean factory + * @see #getExecutorQualifier(Method) * @see #setBeanFactory(BeanFactory) */ public void setExecutor(Executor defaultExecutor) { @@ -128,26 +144,32 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { /** * Determine the specific executor to use when executing the given method. * Should preferably return an {@link AsyncListenableTaskExecutor} implementation. - * @return the executor to use (or {@code null}, but just if no default executor has been set) + * @return the executor to use (or {@code null}, but just if no default executor is available) */ protected AsyncTaskExecutor determineAsyncExecutor(Method method) { AsyncTaskExecutor executor = this.executors.get(method); if (executor == null) { - Executor executorToUse = this.defaultExecutor; + Executor targetExecutor; String qualifier = getExecutorQualifier(method); if (StringUtils.hasLength(qualifier)) { - if (this.beanFactory == null) { - throw new IllegalStateException("BeanFactory must be set on " + getClass().getSimpleName() + - " to access qualified executor '" + qualifier + "'"); + targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier); + } + else { + targetExecutor = this.defaultExecutor; + if (targetExecutor == null) { + synchronized (this.executors) { + if (this.defaultExecutor == null) { + this.defaultExecutor = getDefaultExecutor(this.beanFactory); + } + targetExecutor = this.defaultExecutor; + } } - executorToUse = BeanFactoryAnnotationUtils.qualifiedBeanOfType( - this.beanFactory, Executor.class, qualifier); } - else if (executorToUse == null) { + if (targetExecutor == null) { return null; } - executor = (executorToUse instanceof AsyncListenableTaskExecutor ? - (AsyncListenableTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse)); + executor = (targetExecutor instanceof AsyncListenableTaskExecutor ? + (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor)); this.executors.put(method, executor); } return executor; @@ -160,11 +182,69 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { * been specified and that the {@linkplain #setExecutor(Executor) default executor} * should be used. * @param method the method to inspect for executor qualifier metadata - * @return the qualifier if specified, otherwise empty string or {@code null} + * @return the qualifier if specified, otherwise empty String or {@code null} * @see #determineAsyncExecutor(Method) + * @see #findQualifiedExecutor(BeanFactory, String) */ protected abstract String getExecutorQualifier(Method method); + /** + * Retrieve a target executor for the given qualifier. + * @param qualifier the qualifier to resolve + * @return the target executor, or {@code null} if none available + * @since 4.2.6 + * @see #getExecutorQualifier(Method) + */ + protected Executor findQualifiedExecutor(BeanFactory beanFactory, String qualifier) { + if (beanFactory == null) { + throw new IllegalStateException("BeanFactory must be set on " + getClass().getSimpleName() + + " to access qualified executor '" + qualifier + "'"); + } + return BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, Executor.class, qualifier); + } + + /** + * Retrieve or build a default executor for this advice instance. + * An executor returned from here will be cached for further use. + *

The default implementation searches for a unique {@link TaskExecutor} bean + * in the context, or for an {@link Executor} bean named "taskExecutor" otherwise. + * If neither of the two is resolvable, this implementation will return {@code null}. + * @param beanFactory the BeanFactory to use for a default executor lookup + * @return the default executor, or {@code null} if none available + * @since 4.2.6 + * @see #findQualifiedExecutor(BeanFactory, String) + * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME + */ + protected Executor getDefaultExecutor(BeanFactory beanFactory) { + if (beanFactory != null) { + try { + // Search for TaskExecutor bean... not plain Executor since that would + // match with ScheduledExecutorService as well, which is unusable for + // our purposes here. TaskExecutor is more clearly designed for it. + return beanFactory.getBean(TaskExecutor.class); + } + catch (NoUniqueBeanDefinitionException ex) { + try { + return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + if (logger.isInfoEnabled()) { + logger.info("More than one TaskExecutor bean found within the context, and none is named " + + "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " + + "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound()); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + logger.debug("Could not find default TaskExecutor bean", ex); + // Giving up -> either using local default executor or none at all... + logger.info("No TaskExecutor bean found for async processing"); + } + } + return null; + } + + /** * Delegate for actually executing the given task with the chosen executor. * @param task the task to execute diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index 8aa0c020ac3..a758fd2576e 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -26,9 +26,11 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.Ordered; import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.util.ClassUtils; /** @@ -65,22 +67,27 @@ import org.springframework.util.ClassUtils; */ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered { + /** + * Create a new instance with a default {@link AsyncUncaughtExceptionHandler}. + * @param defaultExecutor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor} + * or {@link java.util.concurrent.ExecutorService}) to delegate to; + * as of 4.2.6, a local executor for this interceptor will be built otherwise + */ + public AsyncExecutionInterceptor(Executor defaultExecutor) { + super(defaultExecutor); + } + /** * Create a new {@code AsyncExecutionInterceptor}. * @param defaultExecutor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor} - * or {@link java.util.concurrent.ExecutorService}) to delegate to + * or {@link java.util.concurrent.ExecutorService}) to delegate to; + * as of 4.2.6, a local executor for this interceptor will be built otherwise * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use */ public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { super(defaultExecutor, exceptionHandler); } - /** - * Create a new instance with a default {@link AsyncUncaughtExceptionHandler}. - */ - public AsyncExecutionInterceptor(Executor defaultExecutor) { - super(defaultExecutor); - } /** * Intercept the given method invocation, submit the actual calling of the method to @@ -136,6 +143,20 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple return null; } + /** + * This implementation searches for a unique {@link org.springframework.core.task.TaskExecutor} + * bean in the context, or for an {@link Executor} bean named "taskExecutor" otherwise. + * If neither of the two is resolvable (e.g. if no {@code BeanFactory} was configured at all), + * this implementation falls back to a newly created {@link SimpleAsyncTaskExecutor} instance + * for local use if no default could be found. + * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME + */ + @Override + protected Executor getDefaultExecutor(BeanFactory beanFactory) { + Executor defaultExecutor = super.getDefaultExecutor(beanFactory); + return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor()); + } + @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj index f11400f05ee..a985f614de1 100644 --- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -54,8 +54,8 @@ public abstract aspect AbstractAsyncExecutionAspect extends AsyncExecutionAspect * Apply around advice to methods matching the {@link #asyncMethod()} pointcut, * submit the actual calling of the method to the correct task executor and return * immediately to the caller. - * @return {@link Future} if the original method returns {@code Future}; {@code null} - * otherwise. + * @return {@link Future} if the original method returns {@code Future}; + * {@code null} otherwise */ @SuppressAjWarnings("adviceDidNotMatch") Object around() : asyncMethod() { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java index c3b0ad453e7..117de48dfbc 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -42,7 +42,8 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor * and a simple {@link AsyncUncaughtExceptionHandler}. * @param defaultExecutor the executor to be used by default if no more specific - * executor has been qualified at the method level using {@link Async#value()} + * executor has been qualified at the method level using {@link Async#value()}; + * as of 4.2.6, a local executor for this interceptor will be built otherwise */ public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) { super(defaultExecutor); @@ -51,7 +52,8 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept /** * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor. * @param defaultExecutor the executor to be used by default if no more specific - * executor has been qualified at the method level using {@link Async#value()} + * executor has been qualified at the method level using {@link Async#value()}; + * as of 4.2.6, a local executor for this interceptor will be built otherwise * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to * handle exceptions thrown by asynchronous method executions with {@code void} * return type @@ -74,7 +76,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept */ @Override protected String getExecutorQualifier(Method method) { - // maintainer's note: changes made here should also be made in + // Maintainer's note: changes made here should also be made in // AnnotationAsyncExecutionAspect#getExecutorQualifier Async async = AnnotationUtils.findAnnotation(method, Async.class); if (async == null) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java index 310c8810aea..ff321cf8e74 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -32,7 +32,6 @@ import org.springframework.aop.support.ComposablePointcut; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -73,8 +72,10 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B /** * Create a new {@code AsyncAnnotationAdvisor} for the given task executor. * @param executor the task executor to use for asynchronous methods - * @param exceptionHandler the {@link org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler} to use to + * (can be {@code null} to trigger default executor resolution) + * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to * handle unexpected exception thrown by asynchronous method executions + * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) */ @SuppressWarnings("unchecked") public AsyncAnnotationAdvisor(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) { @@ -87,9 +88,6 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B catch (ClassNotFoundException ex) { // If EJB 3.1 API not present, simply ignore. } - if (executor == null) { - executor = new SimpleAsyncTaskExecutor(); - } if (exceptionHandler != null) { this.exceptionHandler = exceptionHandler; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java index 69c969a5768..6d699b3961a 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.core.task.TaskExecutor; import org.springframework.util.Assert; @@ -68,8 +66,10 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd *

Note that the initial lookup happens by type; this is just the fallback * in case of multiple executor beans found in the context. * @since 4.2 + * @see AnnotationAsyncExecutionInterceptor#DEFAULT_TASK_EXECUTOR_BEAN_NAME */ - public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor"; + public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = + AnnotationAsyncExecutionInterceptor.DEFAULT_TASK_EXECUTOR_BEAN_NAME; protected final Log logger = LogFactory.getLog(getClass()); @@ -102,6 +102,13 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd /** * Set the {@link Executor} to use when invoking methods asynchronously. + *

If not specified, default executor resolution will apply: searching for a + * unique {@link TaskExecutor} bean in the context, or for an {@link Executor} + * bean named "taskExecutor" otherwise. If neither of the two is resolvable, + * a local default executor will be created within the interceptor. + * @see AsyncAnnotationAdvisor#AsyncAnnotationAdvisor(Executor, AsyncUncaughtExceptionHandler) + * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory) + * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME */ public void setExecutor(Executor executor) { this.executor = executor; @@ -121,31 +128,7 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); - Executor executorToUse = this.executor; - if (executorToUse == null) { - try { - // Search for TaskExecutor bean... not plain Executor since that would - // match with ScheduledExecutorService as well, which is unusable for - // our purposes here. TaskExecutor is more clearly designed for it. - executorToUse = beanFactory.getBean(TaskExecutor.class); - } - catch (NoUniqueBeanDefinitionException ex) { - try { - executorToUse = beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); - } - catch (NoSuchBeanDefinitionException ex2) { - logger.info("More than one TaskExecutor bean found within the context, and none is " + - "named 'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' " + - "(possibly as an alias) in order to use it for async annotation processing."); - } - } - catch (NoSuchBeanDefinitionException ex) { - logger.info("No TaskExecutor bean found for async annotation processing."); - // Giving up -> falling back to default executor within the advisor... - } - } - - AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(executorToUse, this.exceptionHandler); + AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler); if (this.asyncAnnotationType != null) { advisor.setAsyncAnnotationType(this.asyncAnnotationType); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java index ad7c3b0416e..ef3c396e646 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -41,26 +41,28 @@ import org.springframework.core.Ordered; * @Configuration * @EnableAsync * public class AppConfig { + * * @Bean * public MyAsyncBean asyncBean() { * return new MyAsyncBean(); * } * } * - * *

The {@link #mode} attribute controls how advice is applied; if the mode is * {@link AdviceMode#PROXY} (the default), then the other attributes control the behavior * of the proxying. * - *

Note that if the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then - * the value of the {@link #proxyTargetClass} attribute will be ignored. Note also - * that in this case the {@code spring-aspects} module JAR must be present on the - * classpath. + *

Note that if the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then the + * value of the {@link #proxyTargetClass} attribute will be ignored. Note also that in + * this case the {@code spring-aspects} module JAR must be present on the classpath. * - *

By default, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor - * SimpleAsyncTaskExecutor} will be used to process async method invocations. Besides, - * annotated methods having a {@code void} return type cannot transmit any exception - * back to the caller. By default, such uncaught exceptions are only logged. + *

By default, Spring will be searching for an associated thread pool definition: + * either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context, + * or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise. If + * neither of the two is resolvable, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} + * will be used to process async method invocations. Besides, annotated methods having a + * {@code void} return type cannot transmit any exception back to the caller. By default, + * such uncaught exceptions are only logged. * *

To customize all this, implement {@link AsyncConfigurer} and * provide: @@ -129,6 +131,7 @@ import org.springframework.core.Ordered; * through direct access to actual componentry. * * @author Chris Beams + * @author Juergen Hoeller * @author Stephane Nicoll * @author Sam Brannen * @since 3.1 @@ -175,9 +178,8 @@ public @interface EnableAsync { AdviceMode mode() default AdviceMode.PROXY; /** - * Indicate the order in which the - * {@link org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor - * AsyncAnnotationBeanPostProcessor} should be applied. + * Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor} + * should be applied. *

The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run * after all other post-processors, so that it can add an advisor to * existing proxies rather than double-proxy. diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java index 944ca9a6bd0..b5f643f31b2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,16 +37,18 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * @Configuration * @EnableScheduling * public class AppConfig { + * * // various @Bean definitions * } * * This enables detection of @{@link Scheduled} annotations on any Spring-managed - * bean in the container. For example, given a class {@code MyTask} + * bean in the container. For example, given a class {@code MyTask} * *

  * package com.myco.tasks;
  *
  * public class MyTask {
+ *
  *     @Scheduled(fixedRate=1000)
  *     public void work() {
  *         // task execution logic
@@ -60,6 +62,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
  * @Configuration
  * @EnableScheduling
  * public class AppConfig {
+ *
  *     @Bean
  *     public MyTask task() {
  *         return new MyTask();
@@ -84,14 +87,21 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
  * @Configuration
  * @EnableScheduling
  * public class AppConfig {
+ *
  *     @Scheduled(fixedRate=1000)
  *     public void work() {
  *         // task execution logic
  *     }
  * }
* - * In all of the above scenarios, a default single-threaded task executor is used. - * When more control is desired, a {@code @Configuration} class may implement + *

By default, will be searching for an associated scheduler definition: either + * a unique {@link org.springframework.scheduling.TaskScheduler} bean in the context, + * or a {@code TaskScheduler} bean named "taskScheduler" otherwise; the same lookup + * will also be performed for a {@link java.util.concurrent.ScheduledExecutorService} + * bean. If neither of the two is resolvable, a local single-threaded default + * scheduler will be created and used within the registrar. + * + *

When more control is desired, a {@code @Configuration} class may implement * {@link SchedulingConfigurer}. This allows access to the underlying * {@link ScheduledTaskRegistrar} instance. For example, the following example * demonstrates how to customize the {@link Executor} used to execute scheduled @@ -101,6 +111,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * @Configuration * @EnableScheduling * public class AppConfig implements SchedulingConfigurer { + * * @Override * public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { * taskRegistrar.setScheduler(taskExecutor()); @@ -112,11 +123,11 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * } * } * - * Note in the example above the use of {@code @Bean(destroyMethod="shutdown")}. This - * ensures that the task executor is properly shut down when the Spring application - * context itself is closed. + *

Note in the example above the use of {@code @Bean(destroyMethod="shutdown")}. + * This ensures that the task executor is properly shut down when the Spring + * application context itself is closed. * - * Implementing {@code SchedulingConfigurer} also allows for fine-grained + *

Implementing {@code SchedulingConfigurer} also allows for fine-grained * control over task registration via the {@code ScheduledTaskRegistrar}. * For example, the following configures the execution of a particular bean * method per a custom {@code Trigger} implementation: @@ -125,6 +136,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * @Configuration * @EnableScheduling * public class AppConfig implements SchedulingConfigurer { + * * @Override * public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { * taskRegistrar.setScheduler(taskScheduler()); @@ -167,6 +179,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar; * through direct access to actual componentry.

* * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 * @see Scheduled * @see SchedulingConfiguration diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index 9aa2c9c7ade..c455380b2c2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -89,6 +89,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, * The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler". *

Note that the initial lookup happens by type; this is just the fallback * in case of multiple scheduler beans found in the context. + * @since 4.2 */ public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler"; @@ -118,6 +119,12 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService} * to be wrapped as a TaskScheduler. + *

If not specified, default scheduler resolution will apply: searching for a + * unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler} + * bean named "taskScheduler" otherwise; the same lookup will also be performed for + * a {@link ScheduledExecutorService} bean. If neither of the two is resolvable, + * a local single-threaded default scheduler will be created within the registrar. + * @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME */ public void setScheduler(Object scheduler) { this.scheduler = scheduler; @@ -195,10 +202,13 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class)); } catch (NoSuchBeanDefinitionException ex2) { - throw new IllegalStateException("More than one TaskScheduler bean exists within the context, and " + - "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "+ - "(possibly as an alias); or implement the SchedulingConfigurer interface and call " + - "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex); + if (logger.isInfoEnabled()) { + logger.info("More than one TaskScheduler bean exists within the context, and " + + "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " + + "(possibly as an alias); or implement the SchedulingConfigurer interface and call " + + "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " + + ex.getBeanNamesFound()); + } } } catch (NoSuchBeanDefinitionException ex) { @@ -208,14 +218,24 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class)); } catch (NoUniqueBeanDefinitionException ex2) { - throw new IllegalStateException("More than one ScheduledExecutorService bean exists within " + - "the context. Mark one of them as primary; or implement the SchedulingConfigurer " + - "interface and call ScheduledTaskRegistrar#setScheduler explicitly within the " + - "configureTasks() callback.", ex); + try { + this.registrar.setScheduler( + this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, ScheduledExecutorService.class)); + } + catch (NoSuchBeanDefinitionException ex3) { + if (logger.isInfoEnabled()) { + logger.info("More than one ScheduledExecutorService bean exists within the context, and " + + "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " + + "(possibly as an alias); or implement the SchedulingConfigurer interface and call " + + "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " + + ex2.getBeanNamesFound()); + } + } } catch (NoSuchBeanDefinitionException ex2) { - logger.debug("Could not find default ScheduledExecutorService bean", ex); + logger.debug("Could not find default ScheduledExecutorService bean", ex2); // Giving up -> falling back to default scheduler within the registrar... + logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing"); } } }