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