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