001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.scheduling.annotation;
018
019import java.lang.annotation.Annotation;
020import java.util.HashSet;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import java.util.concurrent.Executor;
024import java.util.function.Supplier;
025
026import org.aopalliance.aop.Advice;
027
028import org.springframework.aop.Pointcut;
029import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
030import org.springframework.aop.support.AbstractPointcutAdvisor;
031import org.springframework.aop.support.ComposablePointcut;
032import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
033import org.springframework.beans.factory.BeanFactory;
034import org.springframework.beans.factory.BeanFactoryAware;
035import org.springframework.lang.Nullable;
036import org.springframework.util.Assert;
037import org.springframework.util.ClassUtils;
038import org.springframework.util.function.SingletonSupplier;
039
040/**
041 * Advisor that activates asynchronous method execution through the {@link Async}
042 * annotation. This annotation can be used at the method and type level in
043 * implementation classes as well as in service interfaces.
044 *
045 * <p>This advisor detects the EJB 3.1 {@code javax.ejb.Asynchronous}
046 * annotation as well, treating it exactly like Spring's own {@code Async}.
047 * Furthermore, a custom async annotation type may get specified through the
048 * {@link #setAsyncAnnotationType "asyncAnnotationType"} property.
049 *
050 * @author Juergen Hoeller
051 * @since 3.0
052 * @see Async
053 * @see AnnotationAsyncExecutionInterceptor
054 */
055@SuppressWarnings("serial")
056public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
057
058        private Advice advice;
059
060        private Pointcut pointcut;
061
062
063        /**
064         * Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
065         */
066        public AsyncAnnotationAdvisor() {
067                this((Supplier<Executor>) null, (Supplier<AsyncUncaughtExceptionHandler>) null);
068        }
069
070        /**
071         * Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
072         * @param executor the task executor to use for asynchronous methods
073         * (can be {@code null} to trigger default executor resolution)
074         * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
075         * handle unexpected exception thrown by asynchronous method executions
076         * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory)
077         */
078        @SuppressWarnings("unchecked")
079        public AsyncAnnotationAdvisor(
080                        @Nullable Executor executor, @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {
081
082                this(SingletonSupplier.ofNullable(executor), SingletonSupplier.ofNullable(exceptionHandler));
083        }
084
085        /**
086         * Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
087         * @param executor the task executor to use for asynchronous methods
088         * (can be {@code null} to trigger default executor resolution)
089         * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
090         * handle unexpected exception thrown by asynchronous method executions
091         * @since 5.1
092         * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory)
093         */
094        @SuppressWarnings("unchecked")
095        public AsyncAnnotationAdvisor(
096                        @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
097
098                Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
099                asyncAnnotationTypes.add(Async.class);
100                try {
101                        asyncAnnotationTypes.add((Class<? extends Annotation>)
102                                        ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
103                }
104                catch (ClassNotFoundException ex) {
105                        // If EJB 3.1 API not present, simply ignore.
106                }
107                this.advice = buildAdvice(executor, exceptionHandler);
108                this.pointcut = buildPointcut(asyncAnnotationTypes);
109        }
110
111
112        /**
113         * Set the 'async' annotation type.
114         * <p>The default async annotation type is the {@link Async} annotation, as well
115         * as the EJB 3.1 {@code javax.ejb.Asynchronous} annotation (if present).
116         * <p>This setter property exists so that developers can provide their own
117         * (non-Spring-specific) annotation type to indicate that a method is to
118         * be executed asynchronously.
119         * @param asyncAnnotationType the desired annotation type
120         */
121        public void setAsyncAnnotationType(Class<? extends Annotation> asyncAnnotationType) {
122                Assert.notNull(asyncAnnotationType, "'asyncAnnotationType' must not be null");
123                Set<Class<? extends Annotation>> asyncAnnotationTypes = new HashSet<>();
124                asyncAnnotationTypes.add(asyncAnnotationType);
125                this.pointcut = buildPointcut(asyncAnnotationTypes);
126        }
127
128        /**
129         * Set the {@code BeanFactory} to be used when looking up executors by qualifier.
130         */
131        @Override
132        public void setBeanFactory(BeanFactory beanFactory) {
133                if (this.advice instanceof BeanFactoryAware) {
134                        ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
135                }
136        }
137
138
139        @Override
140        public Advice getAdvice() {
141                return this.advice;
142        }
143
144        @Override
145        public Pointcut getPointcut() {
146                return this.pointcut;
147        }
148
149
150        protected Advice buildAdvice(
151                        @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
152
153                AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
154                interceptor.configure(executor, exceptionHandler);
155                return interceptor;
156        }
157
158        /**
159         * Calculate a pointcut for the given async annotation types, if any.
160         * @param asyncAnnotationTypes the async annotation types to introspect
161         * @return the applicable Pointcut object, or {@code null} if none
162         */
163        protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {
164                ComposablePointcut result = null;
165                for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {
166                        Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
167                        Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);
168                        if (result == null) {
169                                result = new ComposablePointcut(cpc);
170                        }
171                        else {
172                                result.union(cpc);
173                        }
174                        result = result.union(mpc);
175                }
176                return (result != null ? result : Pointcut.TRUE);
177        }
178
179}