/*
 * Copyright 2002-2018 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
 *
 *      https://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.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.Supplier;

import org.apache.commons.logging.Log;
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.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.function.SingletonSupplier;

Base class for asynchronous method execution aspects, such as org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor or org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect.

Provides support for executor qualification on a method-by-method basis. AsyncExecutionAspectSupport objects must be constructed with a default Executor, but each individual method may further qualify a specific Executor bean to be used when executing it, e.g. through an annotation attribute.

Author:Chris Beams, Juergen Hoeller, Stephane Nicoll
Since:3.1.2
/** * Base class for asynchronous method execution aspects, such as * {@code org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor} * or {@code org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}. * * <p>Provides support for <i>executor qualification</i> on a method-by-method basis. * {@code AsyncExecutionAspectSupport} objects must be constructed with a default {@code * Executor}, but each individual method may further qualify a specific {@code Executor} * bean to be used when executing it, e.g. through an annotation attribute. * * @author Chris Beams * @author Juergen Hoeller * @author Stephane Nicoll * @since 3.1.2 */
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
The default name of the 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
/** * 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"; protected final Log logger = LogFactory.getLog(getClass()); private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<>(16); private SingletonSupplier<Executor> defaultExecutor; private SingletonSupplier<AsyncUncaughtExceptionHandler> exceptionHandler; @Nullable private BeanFactory beanFactory;
Create a new instance with a default AsyncUncaughtExceptionHandler.
Params:
  • defaultExecutor – the Executor (typically a Spring AsyncTaskExecutor or 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
/** * 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(@Nullable Executor defaultExecutor) { this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); this.exceptionHandler = SingletonSupplier.of(SimpleAsyncUncaughtExceptionHandler::new); }
Create a new AsyncExecutionAspectSupport with the given exception handler.
Params:
  • defaultExecutor – the Executor (typically a Spring AsyncTaskExecutor or 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
  • exceptionHandler – the AsyncUncaughtExceptionHandler to use
/** * 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(@Nullable Executor defaultExecutor, AsyncUncaughtExceptionHandler exceptionHandler) { this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); this.exceptionHandler = SingletonSupplier.of(exceptionHandler); }
Configure this aspect with the given executor and exception handler suppliers, applying the corresponding default if a supplier is not resolvable.
Since:5.1
/** * Configure this aspect with the given executor and exception handler suppliers, * applying the corresponding default if a supplier is not resolvable. * @since 5.1 */
public void configure(@Nullable Supplier<Executor> defaultExecutor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) { this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory)); this.exceptionHandler = new SingletonSupplier<>(exceptionHandler, SimpleAsyncUncaughtExceptionHandler::new); }
Supply the executor to be used when executing async methods.
Params:
  • defaultExecutor – the Executor (typically a Spring AsyncTaskExecutor or 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 Also:
/** * 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(Method) * @see #setBeanFactory(BeanFactory) * @see #getDefaultExecutor(BeanFactory) */
public void setExecutor(Executor defaultExecutor) { this.defaultExecutor = SingletonSupplier.of(defaultExecutor); }
Supply the AsyncUncaughtExceptionHandler to use to handle exceptions thrown by invoking asynchronous methods with a void return type.
/** * Supply the {@link AsyncUncaughtExceptionHandler} to use to handle exceptions * thrown by invoking asynchronous methods with a {@code void} return type. */
public void setExceptionHandler(AsyncUncaughtExceptionHandler exceptionHandler) { this.exceptionHandler = SingletonSupplier.of(exceptionHandler); }
Set the BeanFactory to be used when looking up executors by qualifier or when relying on the default executor lookup algorithm.
See Also:
/** * Set the {@link BeanFactory} to be used when looking up executors by qualifier * or when relying on the default executor lookup algorithm. * @see #findQualifiedExecutor(BeanFactory, String) * @see #getDefaultExecutor(BeanFactory) */
@Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; }
Determine the specific executor to use when executing the given method. Should preferably return an AsyncListenableTaskExecutor implementation.
Returns:the executor to use (or null, but just if no default executor is available)
/** * 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 is available) */
@Nullable protected AsyncTaskExecutor determineAsyncExecutor(Method method) { AsyncTaskExecutor executor = this.executors.get(method); if (executor == null) { Executor targetExecutor; String qualifier = getExecutorQualifier(method); if (StringUtils.hasLength(qualifier)) { targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier); } else { targetExecutor = this.defaultExecutor.get(); } if (targetExecutor == null) { return null; } executor = (targetExecutor instanceof AsyncListenableTaskExecutor ? (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor)); this.executors.put(method, executor); } return executor; }
Return the qualifier or bean name of the executor to be used when executing the given async method, typically specified in the form of an annotation attribute. Returning an empty string or null indicates that no specific executor has been specified and that the default executor should be used.
Params:
  • method – the method to inspect for executor qualifier metadata
See Also:
Returns:the qualifier if specified, otherwise empty String or null
/** * Return the qualifier or bean name of the executor to be used when executing the * given async method, typically specified in the form of an annotation attribute. * Returning an empty string or {@code null} indicates that no specific executor has * 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} * @see #determineAsyncExecutor(Method) * @see #findQualifiedExecutor(BeanFactory, String) */
@Nullable protected abstract String getExecutorQualifier(Method method);
Retrieve a target executor for the given qualifier.
Params:
  • qualifier – the qualifier to resolve
See Also:
Returns:the target executor, or null if none available
Since:4.2.6
/** * 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) */
@Nullable protected Executor findQualifiedExecutor(@Nullable 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 TaskExecutor bean in the context, or for an Executor bean named "taskExecutor" otherwise. If neither of the two is resolvable, this implementation will return null.

Params:
  • beanFactory – the BeanFactory to use for a default executor lookup
See Also:
Returns:the default executor, or null if none available
Since:4.2.6
/** * 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 */
@Nullable protected Executor getDefaultExecutor(@Nullable 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) { logger.debug("Could not find unique TaskExecutor bean", 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); try { return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class); } catch (NoSuchBeanDefinitionException ex2) { logger.info("No task executor bean found for async processing: " + "no bean of type TaskExecutor and no bean named 'taskExecutor' either"); } // Giving up -> either using local default executor or none at all... } } return null; }
Delegate for actually executing the given task with the chosen executor.
Params:
  • task – the task to execute
  • executor – the chosen executor
  • returnType – the declared return type (potentially a Future variant)
Returns:the execution result (potentially a corresponding Future handle)
/** * Delegate for actually executing the given task with the chosen executor. * @param task the task to execute * @param executor the chosen executor * @param returnType the declared return type (potentially a {@link Future} variant) * @return the execution result (potentially a corresponding {@link Future} handle) */
@Nullable protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) { if (CompletableFuture.class.isAssignableFrom(returnType)) { return CompletableFuture.supplyAsync(() -> { try { return task.call(); } catch (Throwable ex) { throw new CompletionException(ex); } }, executor); } else if (ListenableFuture.class.isAssignableFrom(returnType)) { return ((AsyncListenableTaskExecutor) executor).submitListenable(task); } else if (Future.class.isAssignableFrom(returnType)) { return executor.submit(task); } else { executor.submit(task); return null; } }
Handles a fatal error thrown while asynchronously invoking the specified Method.

If the return type of the method is a Future object, the original exception can be propagated by just throwing it at the higher level. However, for all other cases, the exception will not be transmitted back to the client. In that later case, the current AsyncUncaughtExceptionHandler will be used to manage such exception.

Params:
  • ex – the exception to handle
  • method – the method that was invoked
  • params – the parameters used to invoke the method
/** * Handles a fatal error thrown while asynchronously invoking the specified * {@link Method}. * <p>If the return type of the method is a {@link Future} object, the original * exception can be propagated by just throwing it at the higher level. However, * for all other cases, the exception will not be transmitted back to the client. * In that later case, the current {@link AsyncUncaughtExceptionHandler} will be * used to manage such exception. * @param ex the exception to handle * @param method the method that was invoked * @param params the parameters used to invoke the method */
protected void handleError(Throwable ex, Method method, Object... params) throws Exception { if (Future.class.isAssignableFrom(method.getReturnType())) { ReflectionUtils.rethrowException(ex); } else { // Could not transmit the exception to the caller with default executor try { this.exceptionHandler.obtain().handleUncaughtException(ex, method, params); } catch (Throwable ex2) { logger.warn("Exception handler for async method '" + method.toGenericString() + "' threw unexpected exception itself", ex2); } } } }