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 7122889c3f..ed5fdbeff2 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-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,15 @@ package org.springframework.scheduling.annotation; import java.lang.annotation.Annotation; import java.util.concurrent.Executor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.core.task.TaskExecutor; import org.springframework.util.Assert; @@ -54,10 +59,21 @@ import org.springframework.util.Assert; * @see Async * @see AsyncAnnotationAdvisor * @see #setBeforeExistingAdvisors + * @see ScheduledAnnotationBeanPostProcessor */ @SuppressWarnings("serial") public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostProcessor 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. + */ + public static final String DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor"; + + + protected final Log logger = LogFactory.getLog(getClass()); + private Class asyncAnnotationType; private Executor executor; @@ -100,9 +116,35 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractAdvisingBeanPostPr this.exceptionHandler = exceptionHandler; } + @Override public void setBeanFactory(BeanFactory beanFactory) { - AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler); + 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, TaskExecutor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + throw new IllegalStateException("More than one TaskExecutor bean exists within the context, " + + "and none is named 'taskExecutor'. Mark one of them as primary or name it " + + "'taskExecutor' (possibly as an alias); or specify the AsyncConfigurer interface " + + "and implement getAsyncExecutor() accordingly.", ex); + } + } + catch (NoSuchBeanDefinitionException ex) { + logger.debug("Could not find default TaskExecutor bean", ex); + // Giving up -> falling back to default executor within the advisor... + } + } + + AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(executorToUse, this.exceptionHandler); if (this.asyncAnnotationType != null) { advisor.setAsyncAnnotationType(this.asyncAnnotationType); } 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 0375ae0ac4..560f214d60 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 @@ -82,13 +82,14 @@ import org.springframework.util.StringValueResolver; * @see SchedulingConfigurer * @see org.springframework.scheduling.TaskScheduler * @see org.springframework.scheduling.config.ScheduledTaskRegistrar + * @see AsyncAnnotationBeanPostProcessor */ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware, SmartInitializingSingleton, ApplicationListener, DisposableBean { /** - * The default name of the TaskScheduler bean to pick up: "taskScheduler". + * 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. */ diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index 75ebf30697..e9ff9524e4 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -87,6 +87,60 @@ public class AsyncAnnotationBeanPostProcessorTests { context.close(); } + @Test + public void taskExecutorByBeanType() { + StaticApplicationContext context = new StaticApplicationContext(); + + BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); + context.registerBeanDefinition("postProcessor", processorDefinition); + + BeanDefinition executorDefinition = new RootBeanDefinition(ThreadPoolTaskExecutor.class); + executorDefinition.getPropertyValues().add("threadNamePrefix", "testExecutor"); + context.registerBeanDefinition("myExecutor", executorDefinition); + + BeanDefinition targetDefinition = + new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); + context.registerBeanDefinition("target", targetDefinition); + + context.refresh(); + + ITestBean testBean = context.getBean("target", ITestBean.class); + testBean.test(); + testBean.await(3000); + Thread asyncThread = testBean.getThread(); + assertTrue(asyncThread.getName().startsWith("testExecutor")); + context.close(); + } + + @Test + public void taskExecutorByBeanName() { + StaticApplicationContext context = new StaticApplicationContext(); + + BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); + context.registerBeanDefinition("postProcessor", processorDefinition); + + BeanDefinition executorDefinition = new RootBeanDefinition(ThreadPoolTaskExecutor.class); + executorDefinition.getPropertyValues().add("threadNamePrefix", "testExecutor"); + context.registerBeanDefinition("myExecutor", executorDefinition); + + BeanDefinition executorDefinition2 = new RootBeanDefinition(ThreadPoolTaskExecutor.class); + executorDefinition2.getPropertyValues().add("threadNamePrefix", "testExecutor2"); + context.registerBeanDefinition("taskExecutor", executorDefinition2); + + BeanDefinition targetDefinition = + new RootBeanDefinition(AsyncAnnotationBeanPostProcessorTests.TestBean.class); + context.registerBeanDefinition("target", targetDefinition); + + context.refresh(); + + ITestBean testBean = context.getBean("target", ITestBean.class); + testBean.test(); + testBean.await(3000); + Thread asyncThread = testBean.getThread(); + assertTrue(asyncThread.getName().startsWith("testExecutor2")); + context.close(); + } + @Test public void configuredThroughNamespace() { GenericXmlApplicationContext context = new GenericXmlApplicationContext(); @@ -264,6 +318,7 @@ public class AsyncAnnotationBeanPostProcessorTests { } } + private static class DirectExecutor implements Executor { @Override @@ -272,6 +327,7 @@ public class AsyncAnnotationBeanPostProcessorTests { } } + @Configuration @EnableAsync static class ConfigWithExceptionHandler extends AsyncConfigurerSupport { @@ -292,5 +348,4 @@ public class AsyncAnnotationBeanPostProcessorTests { } } - }