001/*
002 * Copyright 2002-2020 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.aop.aspectj.annotation;
018
019import java.io.Serializable;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.Field;
022import java.lang.reflect.Method;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.List;
027
028import org.aopalliance.aop.Advice;
029import org.aspectj.lang.annotation.After;
030import org.aspectj.lang.annotation.AfterReturning;
031import org.aspectj.lang.annotation.AfterThrowing;
032import org.aspectj.lang.annotation.Around;
033import org.aspectj.lang.annotation.Before;
034import org.aspectj.lang.annotation.DeclareParents;
035import org.aspectj.lang.annotation.Pointcut;
036
037import org.springframework.aop.Advisor;
038import org.springframework.aop.MethodBeforeAdvice;
039import org.springframework.aop.aspectj.AbstractAspectJAdvice;
040import org.springframework.aop.aspectj.AspectJAfterAdvice;
041import org.springframework.aop.aspectj.AspectJAfterReturningAdvice;
042import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice;
043import org.springframework.aop.aspectj.AspectJAroundAdvice;
044import org.springframework.aop.aspectj.AspectJExpressionPointcut;
045import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
046import org.springframework.aop.aspectj.DeclareParentsAdvisor;
047import org.springframework.aop.framework.AopConfigException;
048import org.springframework.aop.support.DefaultPointcutAdvisor;
049import org.springframework.beans.factory.BeanFactory;
050import org.springframework.core.annotation.AnnotationUtils;
051import org.springframework.core.convert.converter.Converter;
052import org.springframework.core.convert.converter.ConvertingComparator;
053import org.springframework.util.ReflectionUtils;
054import org.springframework.util.StringUtils;
055import org.springframework.util.comparator.CompoundComparator;
056import org.springframework.util.comparator.InstanceComparator;
057
058/**
059 * Factory that can create Spring AOP Advisors given AspectJ classes from
060 * classes honoring the AspectJ 5 annotation syntax, using reflection to
061 * invoke the corresponding advice methods.
062 *
063 * @author Rod Johnson
064 * @author Adrian Colyer
065 * @author Juergen Hoeller
066 * @author Ramnivas Laddad
067 * @author Phillip Webb
068 * @since 2.0
069 */
070@SuppressWarnings("serial")
071public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {
072
073        private static final Comparator<Method> METHOD_COMPARATOR;
074
075        static {
076                CompoundComparator<Method> comparator = new CompoundComparator<Method>();
077                comparator.addComparator(new ConvertingComparator<Method, Annotation>(
078                                new InstanceComparator<Annotation>(
079                                                Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
080                                new Converter<Method, Annotation>() {
081                                        @Override
082                                        public Annotation convert(Method method) {
083                                                AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
084                                                return (ann != null ? ann.getAnnotation() : null);
085                                        }
086                                }));
087                comparator.addComparator(new ConvertingComparator<Method, String>(
088                                new Converter<Method, String>() {
089                                        @Override
090                                        public String convert(Method method) {
091                                                return method.getName();
092                                        }
093                                }));
094                METHOD_COMPARATOR = comparator;
095        }
096
097
098        private final BeanFactory beanFactory;
099
100
101        /**
102         * Create a new {@code ReflectiveAspectJAdvisorFactory}.
103         */
104        public ReflectiveAspectJAdvisorFactory() {
105                this(null);
106        }
107
108        /**
109         * Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given
110         * {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances,
111         * for bean pointcut handling as well as consistent {@link ClassLoader} resolution.
112         * @param beanFactory the BeanFactory to propagate (may be {@code null}}
113         * @since 4.3.6
114         * @see AspectJExpressionPointcut#setBeanFactory
115         * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
116         */
117        public ReflectiveAspectJAdvisorFactory(BeanFactory beanFactory) {
118                this.beanFactory = beanFactory;
119        }
120
121
122        @Override
123        public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
124                Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
125                String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
126                validate(aspectClass);
127
128                // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
129                // so that it will only instantiate once.
130                MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
131                                new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
132
133                List<Advisor> advisors = new ArrayList<Advisor>();
134                for (Method method : getAdvisorMethods(aspectClass)) {
135                        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
136                        if (advisor != null) {
137                                advisors.add(advisor);
138                        }
139                }
140
141                // If it's a per target aspect, emit the dummy instantiating aspect.
142                if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
143                        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
144                        advisors.add(0, instantiationAdvisor);
145                }
146
147                // Find introduction fields.
148                for (Field field : aspectClass.getDeclaredFields()) {
149                        Advisor advisor = getDeclareParentsAdvisor(field);
150                        if (advisor != null) {
151                                advisors.add(advisor);
152                        }
153                }
154
155                return advisors;
156        }
157
158        private List<Method> getAdvisorMethods(Class<?> aspectClass) {
159                final List<Method> methods = new ArrayList<Method>();
160                ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() {
161                        @Override
162                        public void doWith(Method method) throws IllegalArgumentException {
163                                // Exclude pointcuts
164                                if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
165                                        methods.add(method);
166                                }
167                        }
168                });
169                Collections.sort(methods, METHOD_COMPARATOR);
170                return methods;
171        }
172
173        /**
174         * Build a {@link org.springframework.aop.aspectj.DeclareParentsAdvisor}
175         * for the given introduction field.
176         * <p>Resulting Advisors will need to be evaluated for targets.
177         * @param introductionField the field to introspect
178         * @return the Advisor instance, or {@code null} if not an Advisor
179         */
180        private Advisor getDeclareParentsAdvisor(Field introductionField) {
181                DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
182                if (declareParents == null) {
183                        // Not an introduction field
184                        return null;
185                }
186
187                if (DeclareParents.class == declareParents.defaultImpl()) {
188                        throw new IllegalStateException("'defaultImpl' attribute must be set on DeclareParents");
189                }
190
191                return new DeclareParentsAdvisor(
192                                introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
193        }
194
195
196        @Override
197        public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
198                        int declarationOrderInAspect, String aspectName) {
199
200                validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
201
202                AspectJExpressionPointcut expressionPointcut = getPointcut(
203                                candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
204                if (expressionPointcut == null) {
205                        return null;
206                }
207
208                return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
209                                this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
210        }
211
212        private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
213                AspectJAnnotation<?> aspectJAnnotation =
214                                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
215                if (aspectJAnnotation == null) {
216                        return null;
217                }
218
219                AspectJExpressionPointcut ajexp =
220                                new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
221                ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
222                ajexp.setBeanFactory(this.beanFactory);
223                return ajexp;
224        }
225
226
227        @Override
228        public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
229                        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
230
231                Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
232                validate(candidateAspectClass);
233
234                AspectJAnnotation<?> aspectJAnnotation =
235                                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
236                if (aspectJAnnotation == null) {
237                        return null;
238                }
239
240                // If we get here, we know we have an AspectJ method.
241                // Check that it's an AspectJ-annotated class
242                if (!isAspect(candidateAspectClass)) {
243                        throw new AopConfigException("Advice must be declared inside an aspect type: " +
244                                        "Offending method '" + candidateAdviceMethod + "' in class [" +
245                                        candidateAspectClass.getName() + "]");
246                }
247
248                if (logger.isDebugEnabled()) {
249                        logger.debug("Found AspectJ method: " + candidateAdviceMethod);
250                }
251
252                AbstractAspectJAdvice springAdvice;
253
254                switch (aspectJAnnotation.getAnnotationType()) {
255                        case AtPointcut:
256                                if (logger.isDebugEnabled()) {
257                                        logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
258                                }
259                                return null;
260                        case AtAround:
261                                springAdvice = new AspectJAroundAdvice(
262                                                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
263                                break;
264                        case AtBefore:
265                                springAdvice = new AspectJMethodBeforeAdvice(
266                                                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
267                                break;
268                        case AtAfter:
269                                springAdvice = new AspectJAfterAdvice(
270                                                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
271                                break;
272                        case AtAfterReturning:
273                                springAdvice = new AspectJAfterReturningAdvice(
274                                                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
275                                AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
276                                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
277                                        springAdvice.setReturningName(afterReturningAnnotation.returning());
278                                }
279                                break;
280                        case AtAfterThrowing:
281                                springAdvice = new AspectJAfterThrowingAdvice(
282                                                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
283                                AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
284                                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
285                                        springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
286                                }
287                                break;
288                        default:
289                                throw new UnsupportedOperationException(
290                                                "Unsupported advice type on method: " + candidateAdviceMethod);
291                }
292
293                // Now to configure the advice...
294                springAdvice.setAspectName(aspectName);
295                springAdvice.setDeclarationOrder(declarationOrder);
296                String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
297                if (argNames != null) {
298                        springAdvice.setArgumentNamesFromStringArray(argNames);
299                }
300                springAdvice.calculateArgumentBindings();
301
302                return springAdvice;
303        }
304
305
306        /**
307         * Synthetic advisor that instantiates the aspect.
308         * Triggered by per-clause pointcut on non-singleton aspect.
309         * The advice has no effect.
310         */
311        @SuppressWarnings("serial")
312        protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor {
313
314                public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) {
315                        super(aif.getAspectMetadata().getPerClausePointcut(), new MethodBeforeAdvice() {
316                                @Override
317                                public void before(Method method, Object[] args, Object target) {
318                                        // Simply instantiate the aspect
319                                        aif.getAspectInstance();
320                                }
321                        });
322                }
323        }
324
325}