Browse Source

Consistent lazy resolution of default executor/scheduler for Async/ScheduledAnnotationBeanPostProcessor

Issue: SPR-14030
pull/1002/head
Juergen Hoeller 10 years ago
parent
commit
155fa3754b
  1. 140
      spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java
  2. 37
      spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java
  3. 6
      spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj
  4. 10
      spring-context/src/main/java/org/springframework/scheduling/annotation/AnnotationAsyncExecutionInterceptor.java
  5. 10
      spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationAdvisor.java
  6. 41
      spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java
  7. 28
      spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java
  8. 29
      spring-context/src/main/java/org/springframework/scheduling/annotation/EnableScheduling.java
  9. 40
      spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java

140
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; 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.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.support.TaskExecutorAdapter; import org.springframework.core.task.support.TaskExecutorAdapter;
import org.springframework.lang.UsesJava8; import org.springframework.lang.UsesJava8;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -58,6 +61,15 @@ import org.springframework.util.concurrent.ListenableFuture;
*/ */
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware { public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
/**
* The default name of the {@link TaskExecutor} bean to pick up: "taskExecutor".
* <p>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? // Java 8's CompletableFuture type present?
private static final boolean completableFuturePresent = ClassUtils.isPresent( private static final boolean completableFuturePresent = ClassUtils.isPresent(
"java.util.concurrent.CompletableFuture", AsyncExecutionInterceptor.class.getClassLoader()); "java.util.concurrent.CompletableFuture", AsyncExecutionInterceptor.class.getClassLoader());
@ -67,7 +79,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<Method, AsyncTaskExecutor>(16); private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<Method, AsyncTaskExecutor>(16);
private Executor defaultExecutor; private volatile Executor defaultExecutor;
private AsyncUncaughtExceptionHandler exceptionHandler; private AsyncUncaughtExceptionHandler exceptionHandler;
@ -75,33 +87,37 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
/** /**
* Create a new {@link AsyncExecutionAspectSupport}, using the provided default * Create a new instance with a default {@link AsyncUncaughtExceptionHandler}.
* executor unless individual async methods indicate via qualifier that a more * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor}
* specific executor should be used. * or {@link java.util.concurrent.ExecutorService}) to delegate to, unless a more specific
* @param defaultExecutor the executor to use when executing asynchronous methods * executor has been requested via a qualifier on the async method, in which case the
* @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use * executor will be looked up at invocation time against the enclosing bean factory
*/ */
public AsyncExecutionAspectSupport(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { public AsyncExecutionAspectSupport(Executor defaultExecutor) {
this.defaultExecutor = defaultExecutor; this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler());
this.exceptionHandler = exceptionHandler;
} }
/** /**
* 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) { public AsyncExecutionAspectSupport(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) {
this(defaultExecutor, new SimpleAsyncUncaughtExceptionHandler()); this.defaultExecutor = defaultExecutor;
this.exceptionHandler = exceptionHandler;
} }
/** /**
* Supply the executor to be used when executing async methods. * Supply the executor to be used when executing async methods.
* @param defaultExecutor the {@code Executor} (typically a Spring {@code * @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor}
* AsyncTaskExecutor} or {@link java.util.concurrent.ExecutorService}) to delegate to * or {@link java.util.concurrent.ExecutorService}) to delegate to, unless a more specific
* unless a more specific executor has been requested via a qualifier on the async * executor has been requested via a qualifier on the async method, in which case the
* method, in which case the executor will be looked up at invocation time against the * executor will be looked up at invocation time against the enclosing bean factory
* enclosing bean factory. * @see #getExecutorQualifier(Method)
* @see #getExecutorQualifier
* @see #setBeanFactory(BeanFactory) * @see #setBeanFactory(BeanFactory)
*/ */
public void setExecutor(Executor defaultExecutor) { 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. * Determine the specific executor to use when executing the given method.
* Should preferably return an {@link AsyncListenableTaskExecutor} implementation. * 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) { protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
AsyncTaskExecutor executor = this.executors.get(method); AsyncTaskExecutor executor = this.executors.get(method);
if (executor == null) { if (executor == null) {
Executor executorToUse = this.defaultExecutor; Executor targetExecutor;
String qualifier = getExecutorQualifier(method); String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) { if (StringUtils.hasLength(qualifier)) {
if (this.beanFactory == null) { targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
throw new IllegalStateException("BeanFactory must be set on " + getClass().getSimpleName() + }
" to access qualified executor '" + 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; return null;
} }
executor = (executorToUse instanceof AsyncListenableTaskExecutor ? executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) executorToUse : new TaskExecutorAdapter(executorToUse)); (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor); this.executors.put(method, executor);
} }
return executor; return executor;
@ -160,11 +182,69 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
* been specified and that the {@linkplain #setExecutor(Executor) default executor} * been specified and that the {@linkplain #setExecutor(Executor) default executor}
* should be used. * should be used.
* @param method the method to inspect for executor qualifier metadata * @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 #determineAsyncExecutor(Method)
* @see #findQualifiedExecutor(BeanFactory, String)
*/ */
protected abstract String getExecutorQualifier(Method method); 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.
* <p>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. * Delegate for actually executing the given task with the chosen executor.
* @param task the task to execute * @param task the task to execute

37
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.BridgeMethodResolver; import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@ -65,22 +67,27 @@ import org.springframework.util.ClassUtils;
*/ */
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered { 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}. * Create a new {@code AsyncExecutionInterceptor}.
* @param defaultExecutor the {@link Executor} (typically a Spring {@link AsyncTaskExecutor} * @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 * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use
*/ */
public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { public AsyncExecutionInterceptor(Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) {
super(defaultExecutor, 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 * 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; 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 @Override
public int getOrder() { public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; return Ordered.HIGHEST_PRECEDENCE;

6
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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, * Apply around advice to methods matching the {@link #asyncMethod()} pointcut,
* submit the actual calling of the method to the correct task executor and return * submit the actual calling of the method to the correct task executor and return
* immediately to the caller. * immediately to the caller.
* @return {@link Future} if the original method returns {@code Future}; {@code null} * @return {@link Future} if the original method returns {@code Future};
* otherwise. * {@code null} otherwise
*/ */
@SuppressAjWarnings("adviceDidNotMatch") @SuppressAjWarnings("adviceDidNotMatch")
Object around() : asyncMethod() { Object around() : asyncMethod() {

10
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor
* and a simple {@link AsyncUncaughtExceptionHandler}. * and a simple {@link AsyncUncaughtExceptionHandler}.
* @param defaultExecutor the executor to be used by default if no more specific * @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) { public AnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
super(defaultExecutor); super(defaultExecutor);
@ -51,7 +52,8 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
/** /**
* Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor. * Create a new {@code AnnotationAsyncExecutionInterceptor} with the given executor.
* @param defaultExecutor the executor to be used by default if no more specific * @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 * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
* handle exceptions thrown by asynchronous method executions with {@code void} * handle exceptions thrown by asynchronous method executions with {@code void}
* return type * return type
@ -74,7 +76,7 @@ public class AnnotationAsyncExecutionInterceptor extends AsyncExecutionIntercept
*/ */
@Override @Override
protected String getExecutorQualifier(Method method) { 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 // AnnotationAsyncExecutionAspect#getExecutorQualifier
Async async = AnnotationUtils.findAnnotation(method, Async.class); Async async = AnnotationUtils.findAnnotation(method, Async.class);
if (async == null) { if (async == null) {

10
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; 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. * Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
* @param executor the task executor to use for asynchronous methods * @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 * handle unexpected exception thrown by asynchronous method executions
* @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public AsyncAnnotationAdvisor(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) { public AsyncAnnotationAdvisor(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) {
@ -87,9 +88,6 @@ public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements B
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
// If EJB 3.1 API not present, simply ignore. // If EJB 3.1 API not present, simply ignore.
} }
if (executor == null) {
executor = new SimpleAsyncTaskExecutor();
}
if (exceptionHandler != null) { if (exceptionHandler != null) {
this.exceptionHandler = exceptionHandler; this.exceptionHandler = exceptionHandler;
} }

41
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.BeanFactory; 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.core.task.TaskExecutor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -68,8 +66,10 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
* <p>Note that the initial lookup happens by type; this is just the fallback * <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple executor beans found in the context. * in case of multiple executor beans found in the context.
* @since 4.2 * @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()); 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. * Set the {@link Executor} to use when invoking methods asynchronously.
* <p>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) { public void setExecutor(Executor executor) {
this.executor = executor; this.executor = executor;
@ -121,31 +128,7 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
public void setBeanFactory(BeanFactory beanFactory) { public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory); super.setBeanFactory(beanFactory);
Executor executorToUse = this.executor; AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
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);
if (this.asyncAnnotationType != null) { if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType); advisor.setAsyncAnnotationType(this.asyncAnnotationType);
} }

28
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -41,26 +41,28 @@ import org.springframework.core.Ordered;
* &#064;Configuration * &#064;Configuration
* &#064;EnableAsync * &#064;EnableAsync
* public class AppConfig { * public class AppConfig {
*
* &#064;Bean * &#064;Bean
* public MyAsyncBean asyncBean() { * public MyAsyncBean asyncBean() {
* return new MyAsyncBean(); * return new MyAsyncBean();
* } * }
* }</pre> * }</pre>
* *
*
* <p>The {@link #mode} attribute controls how advice is applied; if the mode is * <p>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 * {@link AdviceMode#PROXY} (the default), then the other attributes control the behavior
* of the proxying. * of the proxying.
* *
* <p>Note that if the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then * <p>Note that if the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then the
* the value of the {@link #proxyTargetClass} attribute will be ignored. Note also * value of the {@link #proxyTargetClass} attribute will be ignored. Note also that in
* that in this case the {@code spring-aspects} module JAR must be present on the * this case the {@code spring-aspects} module JAR must be present on the classpath.
* classpath.
* *
* <p>By default, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor * <p>By default, Spring will be searching for an associated thread pool definition:
* SimpleAsyncTaskExecutor} will be used to process async method invocations. Besides, * either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context,
* annotated methods having a {@code void} return type cannot transmit any exception * or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise. If
* back to the caller. By default, such uncaught exceptions are only logged. * 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.
* *
* <p>To customize all this, implement {@link AsyncConfigurer} and * <p>To customize all this, implement {@link AsyncConfigurer} and
* provide: * provide:
@ -129,6 +131,7 @@ import org.springframework.core.Ordered;
* through direct access to actual componentry. * through direct access to actual componentry.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen * @author Sam Brannen
* @since 3.1 * @since 3.1
@ -175,9 +178,8 @@ public @interface EnableAsync {
AdviceMode mode() default AdviceMode.PROXY; AdviceMode mode() default AdviceMode.PROXY;
/** /**
* Indicate the order in which the * Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor}
* {@link org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor * should be applied.
* AsyncAnnotationBeanPostProcessor} should be applied.
* <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run * <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
* after all other post-processors, so that it can add an advisor to * after all other post-processors, so that it can add an advisor to
* existing proxies rather than double-proxy. * existing proxies rather than double-proxy.

29
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,16 +37,18 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* &#064;Configuration * &#064;Configuration
* &#064;EnableScheduling * &#064;EnableScheduling
* public class AppConfig { * public class AppConfig {
*
* // various &#064;Bean definitions * // various &#064;Bean definitions
* }</pre> * }</pre>
* *
* This enables detection of @{@link Scheduled} annotations on any Spring-managed * 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}
* *
* <pre class="code"> * <pre class="code">
* package com.myco.tasks; * package com.myco.tasks;
* *
* public class MyTask { * public class MyTask {
*
* &#064;Scheduled(fixedRate=1000) * &#064;Scheduled(fixedRate=1000)
* public void work() { * public void work() {
* // task execution logic * // task execution logic
@ -60,6 +62,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* &#064;Configuration * &#064;Configuration
* &#064;EnableScheduling * &#064;EnableScheduling
* public class AppConfig { * public class AppConfig {
*
* &#064;Bean * &#064;Bean
* public MyTask task() { * public MyTask task() {
* return new MyTask(); * return new MyTask();
@ -84,14 +87,21 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* &#064;Configuration * &#064;Configuration
* &#064;EnableScheduling * &#064;EnableScheduling
* public class AppConfig { * public class AppConfig {
*
* &#064;Scheduled(fixedRate=1000) * &#064;Scheduled(fixedRate=1000)
* public void work() { * public void work() {
* // task execution logic * // task execution logic
* } * }
* }</pre> * }</pre>
* *
* In all of the above scenarios, a default single-threaded task executor is used. * <p>By default, will be searching for an associated scheduler definition: either
* When more control is desired, a {@code @Configuration} class may implement * 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.
*
* <p>When more control is desired, a {@code @Configuration} class may implement
* {@link SchedulingConfigurer}. This allows access to the underlying * {@link SchedulingConfigurer}. This allows access to the underlying
* {@link ScheduledTaskRegistrar} instance. For example, the following example * {@link ScheduledTaskRegistrar} instance. For example, the following example
* demonstrates how to customize the {@link Executor} used to execute scheduled * demonstrates how to customize the {@link Executor} used to execute scheduled
@ -101,6 +111,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* &#064;Configuration * &#064;Configuration
* &#064;EnableScheduling * &#064;EnableScheduling
* public class AppConfig implements SchedulingConfigurer { * public class AppConfig implements SchedulingConfigurer {
*
* &#064;Override * &#064;Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { * public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskExecutor()); * taskRegistrar.setScheduler(taskExecutor());
@ -112,11 +123,11 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* } * }
* }</pre> * }</pre>
* *
* Note in the example above the use of {@code @Bean(destroyMethod="shutdown")}. This * <p>Note in the example above the use of {@code @Bean(destroyMethod="shutdown")}.
* ensures that the task executor is properly shut down when the Spring application * This ensures that the task executor is properly shut down when the Spring
* context itself is closed. * application context itself is closed.
* *
* Implementing {@code SchedulingConfigurer} also allows for fine-grained * <p>Implementing {@code SchedulingConfigurer} also allows for fine-grained
* control over task registration via the {@code ScheduledTaskRegistrar}. * control over task registration via the {@code ScheduledTaskRegistrar}.
* For example, the following configures the execution of a particular bean * For example, the following configures the execution of a particular bean
* method per a custom {@code Trigger} implementation: * method per a custom {@code Trigger} implementation:
@ -125,6 +136,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* &#064;Configuration * &#064;Configuration
* &#064;EnableScheduling * &#064;EnableScheduling
* public class AppConfig implements SchedulingConfigurer { * public class AppConfig implements SchedulingConfigurer {
*
* &#064;Override * &#064;Override
* public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { * public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
* taskRegistrar.setScheduler(taskScheduler()); * taskRegistrar.setScheduler(taskScheduler());
@ -167,6 +179,7 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* through direct access to actual componentry.<p> * through direct access to actual componentry.<p>
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller
* @since 3.1 * @since 3.1
* @see Scheduled * @see Scheduled
* @see SchedulingConfiguration * @see SchedulingConfiguration

40
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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". * The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".
* <p>Note that the initial lookup happens by type; this is just the fallback * <p>Note that the initial lookup happens by type; this is just the fallback
* in case of multiple scheduler beans found in the context. * in case of multiple scheduler beans found in the context.
* @since 4.2
*/ */
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler"; 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 * Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke
* the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService} * the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}
* to be wrapped as a TaskScheduler. * to be wrapped as a TaskScheduler.
* <p>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) { public void setScheduler(Object scheduler) {
this.scheduler = scheduler; this.scheduler = scheduler;
@ -195,10 +202,13 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class)); this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class));
} }
catch (NoSuchBeanDefinitionException ex2) { catch (NoSuchBeanDefinitionException ex2) {
throw new IllegalStateException("More than one TaskScheduler bean exists within the context, and " + if (logger.isInfoEnabled()) {
"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' "+ logger.info("More than one TaskScheduler bean exists within the context, and " +
"(possibly as an alias); or implement the SchedulingConfigurer interface and call " + "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback.", ex); "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
ex.getBeanNamesFound());
}
} }
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
@ -208,14 +218,24 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class)); this.registrar.setScheduler(this.beanFactory.getBean(ScheduledExecutorService.class));
} }
catch (NoUniqueBeanDefinitionException ex2) { catch (NoUniqueBeanDefinitionException ex2) {
throw new IllegalStateException("More than one ScheduledExecutorService bean exists within " + try {
"the context. Mark one of them as primary; or implement the SchedulingConfigurer " + this.registrar.setScheduler(
"interface and call ScheduledTaskRegistrar#setScheduler explicitly within the " + this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, ScheduledExecutorService.class));
"configureTasks() callback.", ex); }
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) { 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... // Giving up -> falling back to default scheduler within the registrar...
logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
} }
} }
} }

Loading…
Cancel
Save