All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.aop.interceptor.AsyncExecutionAspectSupport Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.interceptor;

import java.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
 * {@code org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor}
 * or {@code org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect}.
 *
 * 

Provides support for executor qualification 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 {@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"; protected final Log logger = LogFactory.getLog(getClass()); private final Map executors = new ConcurrentHashMap<>(16); private SingletonSupplier defaultExecutor; private SingletonSupplier exceptionHandler; @Nullable private BeanFactory beanFactory; /** * 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 {@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 */ public void configure(@Nullable Supplier defaultExecutor, @Nullable Supplier 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. * @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 {@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 {@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 {@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 {@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. * @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 {@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. * @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 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 * {@link Method}. *

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); } } } }