Skip to content

Commit 36daf06

Browse files
committed
Enhance aspect to support superclass aspects
Signed-off-by: Joshua Chen <[email protected]>
1 parent 8d21661 commit 36daf06

File tree

6 files changed

+188
-99
lines changed

6 files changed

+188
-99
lines changed

spring-aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.aopalliance.intercept.MethodInvocation;
3030
import org.aspectj.lang.JoinPoint;
3131
import org.aspectj.lang.ProceedingJoinPoint;
32+
import org.aspectj.runtime.internal.AroundClosure;
3233
import org.aspectj.weaver.tools.JoinPointMatch;
3334
import org.aspectj.weaver.tools.PointcutParameter;
3435

@@ -57,6 +58,7 @@
5758
* @author Adrian Colyer
5859
* @author Juergen Hoeller
5960
* @author Ramnivas Laddad
61+
* @author Joshua Chen
6062
* @since 2.0
6163
*/
6264
@SuppressWarnings("serial")
@@ -145,6 +147,12 @@ public static JoinPoint currentJoinPoint() {
145147
*/
146148
private int joinPointStaticPartArgumentIndex = -1;
147149

150+
/**
151+
* Index for around closure argument (currently only
152+
* supported at index 0 if present at all).
153+
*/
154+
private int aroundClosureArgumentIndex = -1;
155+
148156
@Nullable
149157
private Map<String, Integer> argumentBindings;
150158

@@ -271,7 +279,7 @@ public void setArgumentNamesFromStringArray(String... argumentNames) {
271279
if (!isVariableName(this.argumentNames[i])) {
272280
throw new IllegalArgumentException(
273281
"'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
274-
this.argumentNames[i] + "' that is not a valid Java identifier");
282+
this.argumentNames[i] + "' that is not a valid Java identifier");
275283
}
276284
}
277285
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
@@ -284,7 +292,7 @@ public void setArgumentNamesFromStringArray(String... argumentNames) {
284292
String[] oldNames = this.argumentNames;
285293
this.argumentNames = new String[oldNames.length + 1];
286294
System.arraycopy(oldNames, 0, this.argumentNames, 0, i);
287-
this.argumentNames[i] = "THIS_JOIN_POINT";
295+
this.argumentNames[i] = "thisJoinPoint";
288296
System.arraycopy(oldNames, i, this.argumentNames, i + 1, oldNames.length - i);
289297
break;
290298
}
@@ -383,9 +391,14 @@ public final void calculateArgumentBindings() {
383391

384392
int numUnboundArgs = this.parameterTypes.length;
385393
Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
386-
if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) ||
387-
maybeBindJoinPointStaticPart(parameterTypes[0])) {
388-
numUnboundArgs--;
394+
for (int i = 0; i < parameterTypes.length; i++) {
395+
if (maybeBindJoinPoint(parameterTypes[i], i) ||
396+
maybeBindProceedingJoinPoint(parameterTypes[i], i) ||
397+
maybeBindJoinPointStaticPart(parameterTypes[i], i) ||
398+
maybeBindAroundClosure(parameterTypes[i], i)
399+
) {
400+
numUnboundArgs--;
401+
}
389402
}
390403

391404
if (numUnboundArgs > 0) {
@@ -396,22 +409,22 @@ public final void calculateArgumentBindings() {
396409
this.argumentsIntrospected = true;
397410
}
398411

399-
private boolean maybeBindJoinPoint(Class<?> candidateParameterType) {
412+
private boolean maybeBindJoinPoint(Class<?> candidateParameterType, int index) {
400413
if (JoinPoint.class == candidateParameterType) {
401-
this.joinPointArgumentIndex = 0;
414+
this.joinPointArgumentIndex = index;
402415
return true;
403416
}
404417
else {
405418
return false;
406419
}
407420
}
408421

409-
private boolean maybeBindProceedingJoinPoint(Class<?> candidateParameterType) {
422+
private boolean maybeBindProceedingJoinPoint(Class<?> candidateParameterType, int index) {
410423
if (ProceedingJoinPoint.class == candidateParameterType) {
411424
if (!supportsProceedingJoinPoint()) {
412425
throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice");
413426
}
414-
this.joinPointArgumentIndex = 0;
427+
this.joinPointArgumentIndex = index;
415428
return true;
416429
}
417430
else {
@@ -423,9 +436,19 @@ protected boolean supportsProceedingJoinPoint() {
423436
return false;
424437
}
425438

426-
private boolean maybeBindJoinPointStaticPart(Class<?> candidateParameterType) {
439+
private boolean maybeBindJoinPointStaticPart(Class<?> candidateParameterType, int index) {
427440
if (JoinPoint.StaticPart.class == candidateParameterType) {
428-
this.joinPointStaticPartArgumentIndex = 0;
441+
this.joinPointStaticPartArgumentIndex = index;
442+
return true;
443+
}
444+
else {
445+
return false;
446+
}
447+
}
448+
449+
private boolean maybeBindAroundClosure(Class<?> candidateParameterType, int index) {
450+
if (AroundClosure.class == candidateParameterType) {
451+
this.aroundClosureArgumentIndex = index;
429452
return true;
430453
}
431454
else {
@@ -480,7 +503,22 @@ private void bindExplicitArguments(int numArgumentsLeftToBind) {
480503

481504
// So we match in number...
482505
int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
483-
for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
506+
if (this.joinPointStaticPartArgumentIndex > -1) {
507+
argumentIndexOffset--;
508+
}
509+
if (this.joinPointArgumentIndex > -1) {
510+
argumentIndexOffset--;
511+
}
512+
if (this.aroundClosureArgumentIndex > -1) {
513+
argumentIndexOffset--;
514+
}
515+
for (int i = 0; i < this.argumentNames.length; i++) {
516+
if (this.joinPointStaticPartArgumentIndex > -1 && i == this.joinPointStaticPartArgumentIndex) {
517+
continue;
518+
}
519+
if (this.joinPointArgumentIndex > -1 && i == this.joinPointArgumentIndex) {
520+
continue;
521+
}
484522
this.argumentBindings.put(this.argumentNames[i], i);
485523
}
486524

@@ -525,17 +563,34 @@ private void configurePointcutParameters(String[] argumentNames, int argumentInd
525563
if (this.throwingName != null) {
526564
numParametersToRemove++;
527565
}
566+
if (this.joinPointStaticPartArgumentIndex > -1) {
567+
numParametersToRemove++;
568+
}
569+
if (this.joinPointArgumentIndex > -1) {
570+
numParametersToRemove++;
571+
}
572+
if (this.aroundClosureArgumentIndex > -1) {
573+
numParametersToRemove++;
574+
}
575+
528576
String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove];
529577
Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length];
530578
Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();
531579

532580
int index = 0;
533581
for (int i = 0; i < argumentNames.length; i++) {
534-
if (i < argumentIndexOffset) {
582+
if (this.aroundClosureArgumentIndex > -1 && i == this.aroundClosureArgumentIndex) {
583+
continue;
584+
}
585+
586+
if (this.joinPointStaticPartArgumentIndex > -1 && i == this.joinPointStaticPartArgumentIndex) {
587+
continue;
588+
}
589+
if (this.joinPointArgumentIndex > -1 && i == this.joinPointArgumentIndex) {
535590
continue;
536591
}
537592
if (argumentNames[i].equals(this.returningName) ||
538-
argumentNames[i].equals(this.throwingName)) {
593+
argumentNames[i].equals(this.throwingName)) {
539594
continue;
540595
}
541596
pointcutParameterNames[index] = argumentNames[i];
@@ -600,7 +655,9 @@ else if (this.joinPointStaticPartArgumentIndex != -1) {
600655
}
601656
}
602657

603-
if (numBound != this.parameterTypes.length) {
658+
// if jp is a ProceedingJoinPoint, can ignore numBound check,
659+
// because use the ProceedingJoinPoint.proceed() method in aj
660+
if (!(jp instanceof ProceedingJoinPoint) && numBound != this.parameterTypes.length) {
604661
throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
605662
" arguments, but only bound " + numBound + " (JoinPointMatch " +
606663
(jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");

spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
* <h3>Algorithm Details</h3>
4949
* <p>This class interprets arguments in the following way:
5050
* <ol>
51-
* <li>If the first parameter of the method is of type {@link JoinPoint}
51+
* <li>If the parameter of the method is of type {@link JoinPoint}
5252
* or {@link ProceedingJoinPoint}, it is assumed to be for passing
5353
* {@code thisJoinPoint} to the advice, and the parameter name will
5454
* be assigned the value {@code "thisJoinPoint"}.</li>
55-
* <li>If the first parameter of the method is of type
55+
* <li>If the parameter of the method is of type
5656
* {@code JoinPoint.StaticPart}, it is assumed to be for passing
5757
* {@code "thisJoinPointStaticPart"} to the advice, and the parameter name
5858
* will be assigned the value {@code "thisJoinPointStaticPart"}.</li>
@@ -115,6 +115,7 @@
115115
*
116116
* @author Adrian Colyer
117117
* @author Juergen Hoeller
118+
* @author Joshua Chen
118119
* @since 2.0
119120
*/
120121
public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer {
@@ -308,22 +309,29 @@ private void bindParameterName(int index, @Nullable String name) {
308309
}
309310

310311
/**
311-
* If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
312+
* If the parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
312313
* parameter name and return true, else return false.
313314
*/
314315
private boolean maybeBindThisJoinPoint() {
315-
if ((this.argumentTypes[0] == JoinPoint.class) || (this.argumentTypes[0] == ProceedingJoinPoint.class)) {
316-
bindParameterName(0, THIS_JOIN_POINT);
317-
return true;
318-
}
319-
else {
320-
return false;
316+
for (int i = 0; i < this.argumentTypes.length; i++) {
317+
if (isUnbound(i) && (this.argumentTypes[i] == JoinPoint.class || this.argumentTypes[i] == ProceedingJoinPoint.class)) {
318+
bindParameterName(i, THIS_JOIN_POINT);
319+
return true;
320+
}
321321
}
322+
return false;
322323
}
323324

325+
/**
326+
* If the parameter is of type JoinPoint.StaticPart, bind "thisJoinPointStaticPart" as
327+
* parameter name.
328+
*/
324329
private void maybeBindThisJoinPointStaticPart() {
325-
if (this.argumentTypes[0] == JoinPoint.StaticPart.class) {
326-
bindParameterName(0, THIS_JOIN_POINT_STATIC_PART);
330+
for (int i = 0; i < this.argumentTypes.length; i++) {
331+
if (isUnbound(i) && this.argumentTypes[i] == JoinPoint.StaticPart.class) {
332+
bindParameterName(i, THIS_JOIN_POINT_STATIC_PART);
333+
return;
334+
}
327335
}
328336
}
329337

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,6 +53,7 @@
5353
* @author Adrian Colyer
5454
* @author Juergen Hoeller
5555
* @author Sam Brannen
56+
* @author Joshua Chen
5657
* @since 2.0
5758
*/
5859
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
@@ -93,6 +94,17 @@ public boolean isAspect(Class<?> clazz) {
9394
@Override
9495
public void validate(Class<?> aspectClass) throws AopConfigException {
9596
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
97+
if (aspectClass.getSuperclass() != null) {
98+
Class<?> currSupperClass = aspectClass;
99+
while (currSupperClass != Object.class) {
100+
AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currSupperClass);
101+
if (ajTypeToCheck.isAspect()) {
102+
ajType = ajTypeToCheck;
103+
break;
104+
}
105+
currSupperClass = currSupperClass.getSuperclass();
106+
}
107+
}
96108
if (!ajType.isAspect()) {
97109
throw new NotAnAtAspectException(aspectClass);
98110
}

spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
4040
*
4141
* @author Rod Johnson
4242
* @author Juergen Hoeller
43+
* @author Joshua Chen
4344
* @since 2.0
4445
* @see org.springframework.aop.aspectj.AspectJExpressionPointcut
4546
*/
@@ -81,23 +82,26 @@ public class AspectMetadata implements Serializable {
8182
public AspectMetadata(Class<?> aspectClass, String aspectName) {
8283
this.aspectName = aspectName;
8384

84-
Class<?> currClass = aspectClass;
85-
AjType<?> ajType = null;
86-
while (currClass != Object.class) {
87-
AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currClass);
88-
if (ajTypeToCheck.isAspect()) {
89-
ajType = ajTypeToCheck;
90-
break;
85+
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
86+
if(aspectClass.getSuperclass() != null) {
87+
Class<?> currSupperClass = aspectClass;
88+
while (currSupperClass != Object.class) {
89+
AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currSupperClass);
90+
if (ajTypeToCheck.isAspect()) {
91+
ajType = ajTypeToCheck;
92+
break;
93+
}
94+
currSupperClass = currSupperClass.getSuperclass();
9195
}
92-
currClass = currClass.getSuperclass();
9396
}
94-
if (ajType == null) {
97+
98+
if (ajType == null || !ajType.isAspect()) {
9599
throw new IllegalArgumentException("Class '" + aspectClass.getName() + "' is not an @AspectJ aspect");
96100
}
97101
if (ajType.getDeclarePrecedence().length > 0) {
98102
throw new IllegalArgumentException("DeclarePrecedence not presently supported in Spring AOP");
99103
}
100-
this.aspectClass = ajType.getJavaClass();
104+
this.aspectClass = aspectClass;
101105
this.ajType = ajType;
102106

103107
switch (this.ajType.getPerClause().getKind()) {

spring-aop/src/test/java/org/springframework/aop/aspectj/AbstractAspectJAdviceTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,31 @@ void setArgumentNamesFromStringArray_withoutJoinPointParameter() {
4545
@Test
4646
void setArgumentNamesFromStringArray_withJoinPointAsFirstParameter() {
4747
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsFirstParameter");
48-
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
48+
assertThat(advice).satisfies(hasArgumentNames("thisJoinPoint", "arg1", "arg2"));
4949
}
5050

5151
@Test
5252
void setArgumentNamesFromStringArray_withJoinPointAsLastParameter() {
5353
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsLastParameter");
54-
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "THIS_JOIN_POINT"));
54+
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "thisJoinPoint"));
5555
}
5656

5757
@Test
5858
void setArgumentNamesFromStringArray_withJoinPointAsMiddleParameter() {
5959
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsMiddleParameter");
60-
assertThat(advice).satisfies(hasArgumentNames("arg1", "THIS_JOIN_POINT", "arg2"));
60+
assertThat(advice).satisfies(hasArgumentNames("arg1", "thisJoinPoint", "arg2"));
6161
}
6262

6363
@Test
6464
void setArgumentNamesFromStringArray_withProceedingJoinPoint() {
6565
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithProceedingJoinPoint");
66-
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
66+
assertThat(advice).satisfies(hasArgumentNames("thisJoinPoint", "arg1", "arg2"));
6767
}
6868

6969
@Test
7070
void setArgumentNamesFromStringArray_withStaticPart() {
7171
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithStaticPart");
72-
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
72+
assertThat(advice).satisfies(hasArgumentNames("thisJoinPoint", "arg1", "arg2"));
7373
}
7474

7575
private Consumer<AbstractAspectJAdvice> hasArgumentNames(String... argumentNames) {

0 commit comments

Comments
 (0)