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