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}