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