001/*
002 * Copyright 2002-2019 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;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Type;
025import java.util.HashMap;
026import java.util.Map;
027
028import org.aopalliance.aop.Advice;
029import org.aopalliance.intercept.MethodInvocation;
030import org.aspectj.lang.JoinPoint;
031import org.aspectj.lang.ProceedingJoinPoint;
032import org.aspectj.weaver.tools.JoinPointMatch;
033import org.aspectj.weaver.tools.PointcutParameter;
034
035import org.springframework.aop.AopInvocationException;
036import org.springframework.aop.MethodMatcher;
037import org.springframework.aop.Pointcut;
038import org.springframework.aop.ProxyMethodInvocation;
039import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
040import org.springframework.aop.support.ComposablePointcut;
041import org.springframework.aop.support.MethodMatchers;
042import org.springframework.aop.support.StaticMethodMatcher;
043import org.springframework.core.DefaultParameterNameDiscoverer;
044import org.springframework.core.ParameterNameDiscoverer;
045import org.springframework.lang.Nullable;
046import org.springframework.util.Assert;
047import org.springframework.util.ClassUtils;
048import org.springframework.util.CollectionUtils;
049import org.springframework.util.ReflectionUtils;
050import org.springframework.util.StringUtils;
051
052/**
053 * Base class for AOP Alliance {@link org.aopalliance.aop.Advice} classes
054 * wrapping an AspectJ aspect or an AspectJ-annotated advice method.
055 *
056 * @author Rod Johnson
057 * @author Adrian Colyer
058 * @author Juergen Hoeller
059 * @author Ramnivas Laddad
060 * @since 2.0
061 */
062@SuppressWarnings("serial")
063public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {
064
065        /**
066         * Key used in ReflectiveMethodInvocation userAttributes map for the current joinpoint.
067         */
068        protected static final String JOIN_POINT_KEY = JoinPoint.class.getName();
069
070
071        /**
072         * Lazily instantiate joinpoint for the current invocation.
073         * Requires MethodInvocation to be bound with ExposeInvocationInterceptor.
074         * <p>Do not use if access is available to the current ReflectiveMethodInvocation
075         * (in an around advice).
076         * @return current AspectJ joinpoint, or through an exception if we're not in a
077         * Spring AOP invocation.
078         */
079        public static JoinPoint currentJoinPoint() {
080                MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
081                if (!(mi instanceof ProxyMethodInvocation)) {
082                        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
083                }
084                ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
085                JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
086                if (jp == null) {
087                        jp = new MethodInvocationProceedingJoinPoint(pmi);
088                        pmi.setUserAttribute(JOIN_POINT_KEY, jp);
089                }
090                return jp;
091        }
092
093
094        private final Class<?> declaringClass;
095
096        private final String methodName;
097
098        private final Class<?>[] parameterTypes;
099
100        protected transient Method aspectJAdviceMethod;
101
102        private final AspectJExpressionPointcut pointcut;
103
104        private final AspectInstanceFactory aspectInstanceFactory;
105
106        /**
107         * The name of the aspect (ref bean) in which this advice was defined
108         * (used when determining advice precedence so that we can determine
109         * whether two pieces of advice come from the same aspect).
110         */
111        private String aspectName = "";
112
113        /**
114         * The order of declaration of this advice within the aspect.
115         */
116        private int declarationOrder;
117
118        /**
119         * This will be non-null if the creator of this advice object knows the argument names
120         * and sets them explicitly.
121         */
122        @Nullable
123        private String[] argumentNames;
124
125        /** Non-null if after throwing advice binds the thrown value. */
126        @Nullable
127        private String throwingName;
128
129        /** Non-null if after returning advice binds the return value. */
130        @Nullable
131        private String returningName;
132
133        private Class<?> discoveredReturningType = Object.class;
134
135        private Class<?> discoveredThrowingType = Object.class;
136
137        /**
138         * Index for thisJoinPoint argument (currently only
139         * supported at index 0 if present at all).
140         */
141        private int joinPointArgumentIndex = -1;
142
143        /**
144         * Index for thisJoinPointStaticPart argument (currently only
145         * supported at index 0 if present at all).
146         */
147        private int joinPointStaticPartArgumentIndex = -1;
148
149        @Nullable
150        private Map<String, Integer> argumentBindings;
151
152        private boolean argumentsIntrospected = false;
153
154        @Nullable
155        private Type discoveredReturningGenericType;
156        // Note: Unlike return type, no such generic information is needed for the throwing type,
157        // since Java doesn't allow exception types to be parameterized.
158
159
160        /**
161         * Create a new AbstractAspectJAdvice for the given advice method.
162         * @param aspectJAdviceMethod the AspectJ-style advice method
163         * @param pointcut the AspectJ expression pointcut
164         * @param aspectInstanceFactory the factory for aspect instances
165         */
166        public AbstractAspectJAdvice(
167                        Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
168
169                Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
170                this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
171                this.methodName = aspectJAdviceMethod.getName();
172                this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
173                this.aspectJAdviceMethod = aspectJAdviceMethod;
174                this.pointcut = pointcut;
175                this.aspectInstanceFactory = aspectInstanceFactory;
176        }
177
178
179        /**
180         * Return the AspectJ-style advice method.
181         */
182        public final Method getAspectJAdviceMethod() {
183                return this.aspectJAdviceMethod;
184        }
185
186        /**
187         * Return the AspectJ expression pointcut.
188         */
189        public final AspectJExpressionPointcut getPointcut() {
190                calculateArgumentBindings();
191                return this.pointcut;
192        }
193
194        /**
195         * Build a 'safe' pointcut that excludes the AspectJ advice method itself.
196         * @return a composable pointcut that builds on the original AspectJ expression pointcut
197         * @see #getPointcut()
198         */
199        public final Pointcut buildSafePointcut() {
200                Pointcut pc = getPointcut();
201                MethodMatcher safeMethodMatcher = MethodMatchers.intersection(
202                                new AdviceExcludingMethodMatcher(this.aspectJAdviceMethod), pc.getMethodMatcher());
203                return new ComposablePointcut(pc.getClassFilter(), safeMethodMatcher);
204        }
205
206        /**
207         * Return the factory for aspect instances.
208         */
209        public final AspectInstanceFactory getAspectInstanceFactory() {
210                return this.aspectInstanceFactory;
211        }
212
213        /**
214         * Return the ClassLoader for aspect instances.
215         */
216        @Nullable
217        public final ClassLoader getAspectClassLoader() {
218                return this.aspectInstanceFactory.getAspectClassLoader();
219        }
220
221        @Override
222        public int getOrder() {
223                return this.aspectInstanceFactory.getOrder();
224        }
225
226
227        /**
228         * Set the name of the aspect (bean) in which the advice was declared.
229         */
230        public void setAspectName(String name) {
231                this.aspectName = name;
232        }
233
234        @Override
235        public String getAspectName() {
236                return this.aspectName;
237        }
238
239        /**
240         * Set the declaration order of this advice within the aspect.
241         */
242        public void setDeclarationOrder(int order) {
243                this.declarationOrder = order;
244        }
245
246        @Override
247        public int getDeclarationOrder() {
248                return this.declarationOrder;
249        }
250
251        /**
252         * Set by creator of this advice object if the argument names are known.
253         * <p>This could be for example because they have been explicitly specified in XML,
254         * or in an advice annotation.
255         * @param argNames comma delimited list of arg names
256         */
257        public void setArgumentNames(String argNames) {
258                String[] tokens = StringUtils.commaDelimitedListToStringArray(argNames);
259                setArgumentNamesFromStringArray(tokens);
260        }
261
262        public void setArgumentNamesFromStringArray(String... args) {
263                this.argumentNames = new String[args.length];
264                for (int i = 0; i < args.length; i++) {
265                        this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
266                        if (!isVariableName(this.argumentNames[i])) {
267                                throw new IllegalArgumentException(
268                                                "'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
269                                                this.argumentNames[i] + "' that is not a valid Java identifier");
270                        }
271                }
272                if (this.argumentNames != null) {
273                        if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
274                                // May need to add implicit join point arg name...
275                                Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
276                                if (firstArgType == JoinPoint.class ||
277                                                firstArgType == ProceedingJoinPoint.class ||
278                                                firstArgType == JoinPoint.StaticPart.class) {
279                                        String[] oldNames = this.argumentNames;
280                                        this.argumentNames = new String[oldNames.length + 1];
281                                        this.argumentNames[0] = "THIS_JOIN_POINT";
282                                        System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
283                                }
284                        }
285                }
286        }
287
288        public void setReturningName(String name) {
289                throw new UnsupportedOperationException("Only afterReturning advice can be used to bind a return value");
290        }
291
292        /**
293         * We need to hold the returning name at this level for argument binding calculations,
294         * this method allows the afterReturning advice subclass to set the name.
295         */
296        protected void setReturningNameNoCheck(String name) {
297                // name could be a variable or a type...
298                if (isVariableName(name)) {
299                        this.returningName = name;
300                }
301                else {
302                        // assume a type
303                        try {
304                                this.discoveredReturningType = ClassUtils.forName(name, getAspectClassLoader());
305                        }
306                        catch (Throwable ex) {
307                                throw new IllegalArgumentException("Returning name '" + name  +
308                                                "' is neither a valid argument name nor the fully-qualified " +
309                                                "name of a Java type on the classpath. Root cause: " + ex);
310                        }
311                }
312        }
313
314        protected Class<?> getDiscoveredReturningType() {
315                return this.discoveredReturningType;
316        }
317
318        @Nullable
319        protected Type getDiscoveredReturningGenericType() {
320                return this.discoveredReturningGenericType;
321        }
322
323        public void setThrowingName(String name) {
324                throw new UnsupportedOperationException("Only afterThrowing advice can be used to bind a thrown exception");
325        }
326
327        /**
328         * We need to hold the throwing name at this level for argument binding calculations,
329         * this method allows the afterThrowing advice subclass to set the name.
330         */
331        protected void setThrowingNameNoCheck(String name) {
332                // name could be a variable or a type...
333                if (isVariableName(name)) {
334                        this.throwingName = name;
335                }
336                else {
337                        // assume a type
338                        try {
339                                this.discoveredThrowingType = ClassUtils.forName(name, getAspectClassLoader());
340                        }
341                        catch (Throwable ex) {
342                                throw new IllegalArgumentException("Throwing name '" + name  +
343                                                "' is neither a valid argument name nor the fully-qualified " +
344                                                "name of a Java type on the classpath. Root cause: " + ex);
345                        }
346                }
347        }
348
349        protected Class<?> getDiscoveredThrowingType() {
350                return this.discoveredThrowingType;
351        }
352
353        private boolean isVariableName(String name) {
354                char[] chars = name.toCharArray();
355                if (!Character.isJavaIdentifierStart(chars[0])) {
356                        return false;
357                }
358                for (int i = 1; i < chars.length; i++) {
359                        if (!Character.isJavaIdentifierPart(chars[i])) {
360                                return false;
361                        }
362                }
363                return true;
364        }
365
366
367        /**
368         * Do as much work as we can as part of the set-up so that argument binding
369         * on subsequent advice invocations can be as fast as possible.
370         * <p>If the first argument is of type JoinPoint or ProceedingJoinPoint then we
371         * pass a JoinPoint in that position (ProceedingJoinPoint for around advice).
372         * <p>If the first argument is of type {@code JoinPoint.StaticPart}
373         * then we pass a {@code JoinPoint.StaticPart} in that position.
374         * <p>Remaining arguments have to be bound by pointcut evaluation at
375         * a given join point. We will get back a map from argument name to
376         * value. We need to calculate which advice parameter needs to be bound
377         * to which argument name. There are multiple strategies for determining
378         * this binding, which are arranged in a ChainOfResponsibility.
379         */
380        public final synchronized void calculateArgumentBindings() {
381                // The simple case... nothing to bind.
382                if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
383                        return;
384                }
385
386                int numUnboundArgs = this.parameterTypes.length;
387                Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
388                if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
389                                maybeBindJoinPointStaticPart(parameterTypes[0])) {
390                        numUnboundArgs--;
391                }
392
393                if (numUnboundArgs > 0) {
394                        // need to bind arguments by name as returned from the pointcut match
395                        bindArgumentsByName(numUnboundArgs);
396                }
397
398                this.argumentsIntrospected = true;
399        }
400
401        private boolean maybeBindJoinPoint(Class<?> candidateParameterType) {
402                if (JoinPoint.class == candidateParameterType) {
403                        this.joinPointArgumentIndex = 0;
404                        return true;
405                }
406                else {
407                        return false;
408                }
409        }
410
411        private boolean maybeBindProceedingJoinPoint(Class<?> candidateParameterType) {
412                if (ProceedingJoinPoint.class == candidateParameterType) {
413                        if (!supportsProceedingJoinPoint()) {
414                                throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice");
415                        }
416                        this.joinPointArgumentIndex = 0;
417                        return true;
418                }
419                else {
420                        return false;
421                }
422        }
423
424        protected boolean supportsProceedingJoinPoint() {
425                return false;
426        }
427
428        private boolean maybeBindJoinPointStaticPart(Class<?> candidateParameterType) {
429                if (JoinPoint.StaticPart.class == candidateParameterType) {
430                        this.joinPointStaticPartArgumentIndex = 0;
431                        return true;
432                }
433                else {
434                        return false;
435                }
436        }
437
438        private void bindArgumentsByName(int numArgumentsExpectingToBind) {
439                if (this.argumentNames == null) {
440                        this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod);
441                }
442                if (this.argumentNames != null) {
443                        // We have been able to determine the arg names.
444                        bindExplicitArguments(numArgumentsExpectingToBind);
445                }
446                else {
447                        throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +
448                                        "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +
449                                        "the argument names were not specified and could not be discovered.");
450                }
451        }
452
453        /**
454         * Create a ParameterNameDiscoverer to be used for argument binding.
455         * <p>The default implementation creates a {@link DefaultParameterNameDiscoverer}
456         * and adds a specifically configured {@link AspectJAdviceParameterNameDiscoverer}.
457         */
458        protected ParameterNameDiscoverer createParameterNameDiscoverer() {
459                // We need to discover them, or if that fails, guess,
460                // and if we can't guess with 100% accuracy, fail.
461                DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
462                AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer =
463                                new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression());
464                adviceParameterNameDiscoverer.setReturningName(this.returningName);
465                adviceParameterNameDiscoverer.setThrowingName(this.throwingName);
466                // Last in chain, so if we're called and we fail, that's bad...
467                adviceParameterNameDiscoverer.setRaiseExceptions(true);
468                discoverer.addDiscoverer(adviceParameterNameDiscoverer);
469                return discoverer;
470        }
471
472        private void bindExplicitArguments(int numArgumentsLeftToBind) {
473                Assert.state(this.argumentNames != null, "No argument names available");
474                this.argumentBindings = new HashMap<>();
475
476                int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount();
477                if (this.argumentNames.length != numExpectedArgumentNames) {
478                        throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames +
479                                        " arguments to bind by name in advice, but actually found " +
480                                        this.argumentNames.length + " arguments.");
481                }
482
483                // So we match in number...
484                int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
485                for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
486                        this.argumentBindings.put(this.argumentNames[i], i);
487                }
488
489                // Check that returning and throwing were in the argument names list if
490                // specified, and find the discovered argument types.
491                if (this.returningName != null) {
492                        if (!this.argumentBindings.containsKey(this.returningName)) {
493                                throw new IllegalStateException("Returning argument name '" + this.returningName +
494                                                "' was not bound in advice arguments");
495                        }
496                        else {
497                                Integer index = this.argumentBindings.get(this.returningName);
498                                this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];
499                                this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index];
500                        }
501                }
502                if (this.throwingName != null) {
503                        if (!this.argumentBindings.containsKey(this.throwingName)) {
504                                throw new IllegalStateException("Throwing argument name '" + this.throwingName +
505                                                "' was not bound in advice arguments");
506                        }
507                        else {
508                                Integer index = this.argumentBindings.get(this.throwingName);
509                                this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];
510                        }
511                }
512
513                // configure the pointcut expression accordingly.
514                configurePointcutParameters(this.argumentNames, argumentIndexOffset);
515        }
516
517        /**
518         * All parameters from argumentIndexOffset onwards are candidates for
519         * pointcut parameters - but returning and throwing vars are handled differently
520         * and must be removed from the list if present.
521         */
522        private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) {
523                int numParametersToRemove = argumentIndexOffset;
524                if (this.returningName != null) {
525                        numParametersToRemove++;
526                }
527                if (this.throwingName != null) {
528                        numParametersToRemove++;
529                }
530                String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];
531                Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];
532                Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();
533
534                int index = 0;
535                for (int i = 0; i < argumentNames.length; i++) {
536                        if (i < argumentIndexOffset) {
537                                continue;
538                        }
539                        if (argumentNames[i].equals(this.returningName) ||
540                                argumentNames[i].equals(this.throwingName)) {
541                                continue;
542                        }
543                        pointcutParameterNames[index] = argumentNames[i];
544                        pointcutParameterTypes[index] = methodParameterTypes[i];
545                        index++;
546                }
547
548                this.pointcut.setParameterNames(pointcutParameterNames);
549                this.pointcut.setParameterTypes(pointcutParameterTypes);
550        }
551
552        /**
553         * Take the arguments at the method execution join point and output a set of arguments
554         * to the advice method.
555         * @param jp the current JoinPoint
556         * @param jpMatch the join point match that matched this execution join point
557         * @param returnValue the return value from the method execution (may be null)
558         * @param ex the exception thrown by the method execution (may be null)
559         * @return the empty array if there are no arguments
560         */
561        protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
562                        @Nullable Object returnValue, @Nullable Throwable ex) {
563
564                calculateArgumentBindings();
565
566                // AMC start
567                Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
568                int numBound = 0;
569
570                if (this.joinPointArgumentIndex != -1) {
571                        adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
572                        numBound++;
573                }
574                else if (this.joinPointStaticPartArgumentIndex != -1) {
575                        adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
576                        numBound++;
577                }
578
579                if (!CollectionUtils.isEmpty(this.argumentBindings)) {
580                        // binding from pointcut match
581                        if (jpMatch != null) {
582                                PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();
583                                for (PointcutParameter parameter : parameterBindings) {
584                                        String name = parameter.getName();
585                                        Integer index = this.argumentBindings.get(name);
586                                        adviceInvocationArgs[index] = parameter.getBinding();
587                                        numBound++;
588                                }
589                        }
590                        // binding from returning clause
591                        if (this.returningName != null) {
592                                Integer index = this.argumentBindings.get(this.returningName);
593                                adviceInvocationArgs[index] = returnValue;
594                                numBound++;
595                        }
596                        // binding from thrown exception
597                        if (this.throwingName != null) {
598                                Integer index = this.argumentBindings.get(this.throwingName);
599                                adviceInvocationArgs[index] = ex;
600                                numBound++;
601                        }
602                }
603
604                if (numBound != this.parameterTypes.length) {
605                        throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
606                                        " arguments, but only bound " + numBound + " (JoinPointMatch " +
607                                        (jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");
608                }
609
610                return adviceInvocationArgs;
611        }
612
613
614        /**
615         * Invoke the advice method.
616         * @param jpMatch the JoinPointMatch that matched this execution join point
617         * @param returnValue the return value from the method execution (may be null)
618         * @param ex the exception thrown by the method execution (may be null)
619         * @return the invocation result
620         * @throws Throwable in case of invocation failure
621         */
622        protected Object invokeAdviceMethod(
623                        @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex)
624                        throws Throwable {
625
626                return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
627        }
628
629        // As above, but in this case we are given the join point.
630        protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
631                        @Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
632
633                return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
634        }
635
636        protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
637                Object[] actualArgs = args;
638                if (this.aspectJAdviceMethod.getParameterCount() == 0) {
639                        actualArgs = null;
640                }
641                try {
642                        ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
643                        // TODO AopUtils.invokeJoinpointUsingReflection
644                        return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
645                }
646                catch (IllegalArgumentException ex) {
647                        throw new AopInvocationException("Mismatch on arguments to advice method [" +
648                                        this.aspectJAdviceMethod + "]; pointcut expression [" +
649                                        this.pointcut.getPointcutExpression() + "]", ex);
650                }
651                catch (InvocationTargetException ex) {
652                        throw ex.getTargetException();
653                }
654        }
655
656        /**
657         * Overridden in around advice to return proceeding join point.
658         */
659        protected JoinPoint getJoinPoint() {
660                return currentJoinPoint();
661        }
662
663        /**
664         * Get the current join point match at the join point we are being dispatched on.
665         */
666        @Nullable
667        protected JoinPointMatch getJoinPointMatch() {
668                MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
669                if (!(mi instanceof ProxyMethodInvocation)) {
670                        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
671                }
672                return getJoinPointMatch((ProxyMethodInvocation) mi);
673        }
674
675        // Note: We can't use JoinPointMatch.getClass().getName() as the key, since
676        // Spring AOP does all the matching at a join point, and then all the invocations.
677        // Under this scenario, if we just use JoinPointMatch as the key, then
678        // 'last man wins' which is not what we want at all.
679        // Using the expression is guaranteed to be safe, since 2 identical expressions
680        // are guaranteed to bind in exactly the same way.
681        @Nullable
682        protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
683                String expression = this.pointcut.getExpression();
684                return (expression != null ? (JoinPointMatch) pmi.getUserAttribute(expression) : null);
685        }
686
687
688        @Override
689        public String toString() {
690                return getClass().getName() + ": advice method [" + this.aspectJAdviceMethod + "]; " +
691                                "aspect name '" + this.aspectName + "'";
692        }
693
694        private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
695                inputStream.defaultReadObject();
696                try {
697                        this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes);
698                }
699                catch (NoSuchMethodException ex) {
700                        throw new IllegalStateException("Failed to find advice method on deserialization", ex);
701                }
702        }
703
704
705        /**
706         * MethodMatcher that excludes the specified advice method.
707         * @see AbstractAspectJAdvice#buildSafePointcut()
708         */
709        private static class AdviceExcludingMethodMatcher extends StaticMethodMatcher {
710
711                private final Method adviceMethod;
712
713                public AdviceExcludingMethodMatcher(Method adviceMethod) {
714                        this.adviceMethod = adviceMethod;
715                }
716
717                @Override
718                public boolean matches(Method method, Class<?> targetClass) {
719                        return !this.adviceMethod.equals(method);
720                }
721
722                @Override
723                public boolean equals(@Nullable Object other) {
724                        if (this == other) {
725                                return true;
726                        }
727                        if (!(other instanceof AdviceExcludingMethodMatcher)) {
728                                return false;
729                        }
730                        AdviceExcludingMethodMatcher otherMm = (AdviceExcludingMethodMatcher) other;
731                        return this.adviceMethod.equals(otherMm.adviceMethod);
732                }
733
734                @Override
735                public int hashCode() {
736                        return this.adviceMethod.hashCode();
737                }
738
739                @Override
740                public String toString() {
741                        return getClass().getName() + ": " + this.adviceMethod;
742                }
743        }
744
745}