1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.DefaultParameterNameDiscoverer;
41 import org.springframework.core.ParameterNameDiscoverer;
42 import org.springframework.util.Assert;
43 import org.springframework.util.ClassUtils;
44 import org.springframework.util.CollectionUtils;
45 import org.springframework.util.ReflectionUtils;
46 import org.springframework.util.StringUtils;
47
48
49
50
51
52
53
54
55
56
57
58 public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation {
59
60
61
62
63 protected static final String JOIN_POINT_KEY = JoinPoint.class.getName();
64
65
66
67
68
69
70
71
72
73
74 public static JoinPoint currentJoinPoint() {
75 MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
76 if (!(mi instanceof ProxyMethodInvocation)) {
77 throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
78 }
79 ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
80 JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
81 if (jp == null) {
82 jp = new MethodInvocationProceedingJoinPoint(pmi);
83 pmi.setUserAttribute(JOIN_POINT_KEY, jp);
84 }
85 return jp;
86 }
87
88
89 protected final Method aspectJAdviceMethod;
90
91
92 private final int adviceInvocationArgumentCount;
93
94 private final AspectJExpressionPointcut pointcut;
95
96 private final AspectInstanceFactory aspectInstanceFactory;
97
98
99
100
101
102
103 private String aspectName;
104
105
106
107
108 private int declarationOrder;
109
110
111
112
113
114 private String[] argumentNames = null;
115
116
117 private String throwingName = null;
118
119
120 private String returningName = null;
121
122 private Class<?> discoveredReturningType = Object.class;
123
124 private Class<?> discoveredThrowingType = Object.class;
125
126
127
128
129
130 private int joinPointArgumentIndex = -1;
131
132
133
134
135
136 private int joinPointStaticPartArgumentIndex = -1;
137
138 private Map<String, Integer> argumentBindings = null;
139
140 private boolean argumentsIntrospected = false;
141
142 private Type discoveredReturningGenericType;
143
144
145
146
147
148
149
150
151
152
153 public AbstractAspectJAdvice(
154 Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
155
156 Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
157 this.aspectJAdviceMethod = aspectJAdviceMethod;
158 this.adviceInvocationArgumentCount = this.aspectJAdviceMethod.getParameterTypes().length;
159 this.pointcut = pointcut;
160 this.aspectInstanceFactory = aspectInstanceFactory;
161 }
162
163
164
165
166
167 public final Method getAspectJAdviceMethod() {
168 return this.aspectJAdviceMethod;
169 }
170
171
172
173
174 public final AspectJExpressionPointcut getPointcut() {
175 calculateArgumentBindings();
176 return this.pointcut;
177 }
178
179
180
181
182
183
184 public final Pointcut buildSafePointcut() {
185 Pointcut pc = getPointcut();
186 MethodMatcher safeMethodMatcher = MethodMatchers.intersection(
187 new AdviceExcludingMethodMatcher(this.aspectJAdviceMethod), pc.getMethodMatcher());
188 return new ComposablePointcut(pc.getClassFilter(), safeMethodMatcher);
189 }
190
191
192
193
194 public final AspectInstanceFactory getAspectInstanceFactory() {
195 return this.aspectInstanceFactory;
196 }
197
198
199
200
201 public final ClassLoader getAspectClassLoader() {
202 return this.aspectInstanceFactory.getAspectClassLoader();
203 }
204
205 @Override
206 public int getOrder() {
207 return this.aspectInstanceFactory.getOrder();
208 }
209
210
211 public void setAspectName(String name) {
212 this.aspectName = name;
213 }
214
215 @Override
216 public String getAspectName() {
217 return this.aspectName;
218 }
219
220
221
222
223 public void setDeclarationOrder(int order) {
224 this.declarationOrder = order;
225 }
226
227 @Override
228 public int getDeclarationOrder() {
229 return this.declarationOrder;
230 }
231
232
233
234
235
236
237
238 public void setArgumentNames(String argNames) {
239 String[] tokens = StringUtils.commaDelimitedListToStringArray(argNames);
240 setArgumentNamesFromStringArray(tokens);
241 }
242
243 public void setArgumentNamesFromStringArray(String... args) {
244 this.argumentNames = new String[args.length];
245 for (int i = 0; i < args.length; i++) {
246 this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
247 if (!isVariableName(this.argumentNames[i])) {
248 throw new IllegalArgumentException(
249 "'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
250 this.argumentNames[i] + "' that is not a valid Java identifier");
251 }
252 }
253 if (argumentNames != null) {
254 if (aspectJAdviceMethod.getParameterTypes().length == argumentNames.length + 1) {
255
256 Class<?> firstArgType = aspectJAdviceMethod.getParameterTypes()[0];
257 if (firstArgType == JoinPoint.class ||
258 firstArgType == ProceedingJoinPoint.class ||
259 firstArgType == JoinPoint.StaticPart.class) {
260 String[] oldNames = argumentNames;
261 argumentNames = new String[oldNames.length + 1];
262 argumentNames[0] = "THIS_JOIN_POINT";
263 System.arraycopy(oldNames, 0, argumentNames, 1, oldNames.length);
264 }
265 }
266 }
267 }
268
269 public void setReturningName(String name) {
270 throw new UnsupportedOperationException("Only afterReturning advice can be used to bind a return value");
271 }
272
273
274
275
276
277 protected void setReturningNameNoCheck(String name) {
278
279 if (isVariableName(name)) {
280 this.returningName = name;
281 }
282 else {
283
284 try {
285 this.discoveredReturningType = ClassUtils.forName(name, getAspectClassLoader());
286 }
287 catch (Throwable ex) {
288 throw new IllegalArgumentException("Returning name '" + name +
289 "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " +
290 "Root cause: " + ex);
291 }
292 }
293 }
294
295 protected Class<?> getDiscoveredReturningType() {
296 return this.discoveredReturningType;
297 }
298
299 protected Type getDiscoveredReturningGenericType() {
300 return this.discoveredReturningGenericType;
301 }
302
303 public void setThrowingName(String name) {
304 throw new UnsupportedOperationException("Only afterThrowing advice can be used to bind a thrown exception");
305 }
306
307
308
309
310
311 protected void setThrowingNameNoCheck(String name) {
312
313 if (isVariableName(name)) {
314 this.throwingName = name;
315 }
316 else {
317
318 try {
319 this.discoveredThrowingType = ClassUtils.forName(name, getAspectClassLoader());
320 }
321 catch (Throwable ex) {
322 throw new IllegalArgumentException("Throwing name '" + name +
323 "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " +
324 "Root cause: " + ex);
325 }
326 }
327 }
328
329 protected Class<?> getDiscoveredThrowingType() {
330 return this.discoveredThrowingType;
331 }
332
333 private boolean isVariableName(String name) {
334 char[] chars = name.toCharArray();
335 if (!Character.isJavaIdentifierStart(chars[0])) {
336 return false;
337 }
338 for (int i = 1; i < chars.length; i++) {
339 if (!Character.isJavaIdentifierPart(chars[i])) {
340 return false;
341 }
342 }
343 return true;
344 }
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360 public synchronized final void calculateArgumentBindings() {
361
362 if (this.argumentsIntrospected || this.adviceInvocationArgumentCount == 0) {
363 return;
364 }
365
366 int numUnboundArgs = this.adviceInvocationArgumentCount;
367 Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
368 if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) {
369 numUnboundArgs--;
370 }
371 else if (maybeBindJoinPointStaticPart(parameterTypes[0])) {
372 numUnboundArgs--;
373 }
374
375 if (numUnboundArgs > 0) {
376
377 bindArgumentsByName(numUnboundArgs);
378 }
379
380 this.argumentsIntrospected = true;
381 }
382
383 private boolean maybeBindJoinPoint(Class<?> candidateParameterType) {
384 if (candidateParameterType.equals(JoinPoint.class)) {
385 this.joinPointArgumentIndex = 0;
386 return true;
387 }
388 else {
389 return false;
390 }
391 }
392
393 private boolean maybeBindProceedingJoinPoint(Class<?> candidateParameterType) {
394 if (candidateParameterType.equals(ProceedingJoinPoint.class)) {
395 if (!supportsProceedingJoinPoint()) {
396 throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice");
397 }
398 this.joinPointArgumentIndex = 0;
399 return true;
400 }
401 else {
402 return false;
403 }
404 }
405
406 protected boolean supportsProceedingJoinPoint() {
407 return false;
408 }
409
410 private boolean maybeBindJoinPointStaticPart(Class<?> candidateParameterType) {
411 if (candidateParameterType.equals(JoinPoint.StaticPart.class)) {
412 this.joinPointStaticPartArgumentIndex = 0;
413 return true;
414 }
415 else {
416 return false;
417 }
418 }
419
420 private void bindArgumentsByName(int numArgumentsExpectingToBind) {
421 if (this.argumentNames == null) {
422 this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod);
423 }
424 if (this.argumentNames != null) {
425
426 bindExplicitArguments(numArgumentsExpectingToBind);
427 }
428 else {
429 throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +
430 "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +
431 "the argument names were not specified and could not be discovered.");
432 }
433 }
434
435
436
437
438
439
440 protected ParameterNameDiscoverer createParameterNameDiscoverer() {
441
442
443 DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
444 AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer =
445 new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression());
446 adviceParameterNameDiscoverer.setReturningName(this.returningName);
447 adviceParameterNameDiscoverer.setThrowingName(this.throwingName);
448
449 adviceParameterNameDiscoverer.setRaiseExceptions(true);
450 discoverer.addDiscoverer(adviceParameterNameDiscoverer);
451 return discoverer;
452 }
453
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
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
471
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
495 configurePointcutParameters(argumentIndexOffset);
496 }
497
498
499
500
501
502
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
535
536
537
538
539
540
541
542 protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) {
543 calculateArgumentBindings();
544
545
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
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
570 if (this.returningName != null) {
571 Integer index = this.argumentBindings.get(this.returningName);
572 adviceInvocationArgs[index] = returnValue;
573 numBound++;
574 }
575
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
596
597
598
599
600
601
602 protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
603 return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
604 }
605
606
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
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
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
635
636 protected JoinPoint getJoinPoint() {
637 return currentJoinPoint();
638 }
639
640
641
642
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
652
653
654
655
656
657 protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
658 return (JoinPointMatch) pmi.getUserAttribute(this.pointcut.getExpression());
659 }
660
661
662 @Override
663 public String toString() {
664 return getClass().getName() + ": advice method [" + this.aspectJAdviceMethod + "]; " +
665 "aspect name '" + this.aspectName + "'";
666 }
667
668
669
670
671
672
673 private static class AdviceExcludingMethodMatcher extends StaticMethodMatcher {
674
675 private final Method adviceMethod;
676
677 public AdviceExcludingMethodMatcher(Method adviceMethod) {
678 this.adviceMethod = adviceMethod;
679 }
680
681 @Override
682 public boolean matches(Method method, Class<?> targetClass) {
683 return !this.adviceMethod.equals(method);
684 }
685
686 @Override
687 public boolean equals(Object other) {
688 if (this == other) {
689 return true;
690 }
691 if (!(other instanceof AdviceExcludingMethodMatcher)) {
692 return false;
693 }
694 AdviceExcludingMethodMatcher otherMm = (AdviceExcludingMethodMatcher) other;
695 return this.adviceMethod.equals(otherMm.adviceMethod);
696 }
697
698 @Override
699 public int hashCode() {
700 return this.adviceMethod.hashCode();
701 }
702 }
703
704 }