Skip to content

Commit fbeecf3

Browse files
committed
Test status quo for invocation order of all advice types
Prior to this commit we did not have tests in place to verify the status quo for the invocation order of all advice types when declared within a single aspect, either via the <aop:aspect> XML namespace or AspectJ auto-proxy support. This commit introduces such tests that demonstrate where such ordering is broken or suboptimal. The only test for which the advice invocation order is correct or at least as expected is the afterAdviceTypes() test method in ReflectiveAspectJAdvisorFactoryTests, where an AOP proxy is hand crafted using ReflectiveAspectJAdvisorFactory without the use of Spring's AspectJPrecedenceComparator. See gh-25186
1 parent 04c8de4 commit fbeecf3

File tree

7 files changed

+124
-44
lines changed

7 files changed

+124
-44
lines changed

integration-tests/src/test/java/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121

22+
import org.aspectj.lang.ProceedingJoinPoint;
2223
import org.junit.jupiter.api.Nested;
2324
import org.junit.jupiter.api.Test;
2425

@@ -45,14 +46,14 @@ class AopNamespaceHandlerAdviceOrderIntegrationTests {
4546
class AfterAdviceFirstTests {
4647

4748
@Test
48-
void afterAdviceIsInvokedFirst(@Autowired Echo echo, @Autowired EchoAspect aspect) throws Exception {
49+
void afterAdviceIsInvokedFirst(@Autowired Echo echo, @Autowired InvocationTrackingAspect aspect) throws Exception {
4950
assertThat(aspect.invocations).isEmpty();
5051
assertThat(echo.echo(42)).isEqualTo(42);
51-
assertThat(aspect.invocations).containsExactly("after", "after returning");
52+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after returning");
5253

5354
aspect.invocations.clear();
5455
assertThatExceptionOfType(Exception.class).isThrownBy(() -> echo.echo(new Exception()));
55-
assertThat(aspect.invocations).containsExactly("after", "after throwing");
56+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after throwing");
5657
}
5758
}
5859

@@ -62,14 +63,14 @@ void afterAdviceIsInvokedFirst(@Autowired Echo echo, @Autowired EchoAspect aspec
6263
class AfterAdviceLastTests {
6364

6465
@Test
65-
void afterAdviceIsInvokedLast(@Autowired Echo echo, @Autowired EchoAspect aspect) throws Exception {
66+
void afterAdviceIsInvokedLast(@Autowired Echo echo, @Autowired InvocationTrackingAspect aspect) throws Exception {
6667
assertThat(aspect.invocations).isEmpty();
6768
assertThat(echo.echo(42)).isEqualTo(42);
68-
assertThat(aspect.invocations).containsExactly("after returning", "after");
69+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after returning", "after");
6970

7071
aspect.invocations.clear();
7172
assertThatExceptionOfType(Exception.class).isThrownBy(() -> echo.echo(new Exception()));
72-
assertThat(aspect.invocations).containsExactly("after throwing", "after");
73+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after throwing", "after");
7374
}
7475
}
7576

@@ -84,18 +85,29 @@ Object echo(Object obj) throws Exception {
8485
}
8586
}
8687

87-
static class EchoAspect {
88+
static class InvocationTrackingAspect {
8889

8990
List<String> invocations = new ArrayList<>();
9091

91-
void echo() {
92+
Object around(ProceedingJoinPoint joinPoint) throws Throwable {
93+
invocations.add("around - start");
94+
try {
95+
return joinPoint.proceed();
96+
}
97+
finally {
98+
invocations.add("around - end");
99+
}
100+
}
101+
102+
void before() {
103+
invocations.add("before");
92104
}
93105

94-
void succeeded() {
106+
void afterReturning() {
95107
invocations.add("after returning");
96108
}
97109

98-
void failed() {
110+
void afterThrowing() {
99111
invocations.add("after throwing");
100112
}
101113

integration-tests/src/test/java/org/springframework/aop/framework/autoproxy/AspectJAutoProxyAdviceOrderIntegrationTests.java

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121

22+
import org.aspectj.lang.ProceedingJoinPoint;
2223
import org.aspectj.lang.annotation.After;
2324
import org.aspectj.lang.annotation.AfterReturning;
2425
import org.aspectj.lang.annotation.AfterThrowing;
26+
import org.aspectj.lang.annotation.Around;
2527
import org.aspectj.lang.annotation.Aspect;
28+
import org.aspectj.lang.annotation.Before;
2629
import org.aspectj.lang.annotation.Pointcut;
2730
import org.junit.jupiter.api.Nested;
2831
import org.junit.jupiter.api.Test;
@@ -47,25 +50,39 @@
4750
*/
4851
class AspectJAutoProxyAdviceOrderIntegrationTests {
4952

53+
/**
54+
* {@link After @After} advice declared as first <em>after</em> method in source code.
55+
*/
5056
@Nested
5157
@SpringJUnitConfig(AfterAdviceFirstConfig.class)
5258
@DirtiesContext
5359
class AfterAdviceFirstTests {
5460

5561
@Test
56-
void afterAdviceIsInvokedFirst(@Autowired Echo echo, @Autowired AfterAdviceFirstAspect aspect) throws Exception {
62+
void afterAdviceIsNotInvokedLast(@Autowired Echo echo, @Autowired AfterAdviceFirstAspect aspect) throws Exception {
5763
assertThat(aspect.invocations).isEmpty();
5864
assertThat(echo.echo(42)).isEqualTo(42);
59-
assertThat(aspect.invocations).containsExactly("after", "after returning");
65+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after returning");
6066

6167
aspect.invocations.clear();
6268
assertThatExceptionOfType(Exception.class).isThrownBy(
6369
() -> echo.echo(new Exception()));
64-
assertThat(aspect.invocations).containsExactly("after", "after throwing");
70+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after throwing");
6571
}
6672
}
6773

6874

75+
/**
76+
* This test class uses {@link AfterAdviceLastAspect} which declares its
77+
* {@link After @After} advice as the last <em>after advice type</em> method
78+
* in its source code.
79+
*
80+
* <p>On Java versions prior to JDK 7, we would have expected the {@code @After}
81+
* advice method to be invoked after {@code @AfterThrowing} and
82+
* {@code @AfterReturning} advice methods due to the AspectJ precedence
83+
* rules implemented in
84+
* {@link org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator}.
85+
*/
6986
@Nested
7087
@SpringJUnitConfig(AfterAdviceLastConfig.class)
7188
@DirtiesContext
@@ -75,14 +92,12 @@ class AfterAdviceLastTests {
7592
void afterAdviceIsNotInvokedLast(@Autowired Echo echo, @Autowired AfterAdviceLastAspect aspect) throws Exception {
7693
assertThat(aspect.invocations).isEmpty();
7794
assertThat(echo.echo(42)).isEqualTo(42);
78-
// On Java versions prior to JDK 7, we would expect the @After advice to be invoked last.
79-
assertThat(aspect.invocations).containsExactly("after", "after returning");
95+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after returning");
8096

8197
aspect.invocations.clear();
8298
assertThatExceptionOfType(Exception.class).isThrownBy(
8399
() -> echo.echo(new Exception()));
84-
// On Java versions prior to JDK 7, we would expect the @After advice to be invoked last.
85-
assertThat(aspect.invocations).containsExactly("after", "after throwing");
100+
assertThat(aspect.invocations).containsExactly("around - start", "before", "around - end", "after", "after throwing");
86101
}
87102
}
88103

@@ -145,14 +160,30 @@ void after() {
145160
}
146161

147162
@AfterReturning("echo()")
148-
void succeeded() {
163+
void afterReturning() {
149164
invocations.add("after returning");
150165
}
151166

152167
@AfterThrowing("echo()")
153-
void failed() {
168+
void afterThrowing() {
154169
invocations.add("after throwing");
155170
}
171+
172+
@Before("echo()")
173+
void before() {
174+
invocations.add("before");
175+
}
176+
177+
@Around("echo()")
178+
Object around(ProceedingJoinPoint joinPoint) throws Throwable {
179+
invocations.add("around - start");
180+
try {
181+
return joinPoint.proceed();
182+
}
183+
finally {
184+
invocations.add("around - end");
185+
}
186+
}
156187
}
157188

158189
/**
@@ -167,13 +198,29 @@ static class AfterAdviceLastAspect {
167198
void echo() {
168199
}
169200

201+
@Around("echo()")
202+
Object around(ProceedingJoinPoint joinPoint) throws Throwable {
203+
invocations.add("around - start");
204+
try {
205+
return joinPoint.proceed();
206+
}
207+
finally {
208+
invocations.add("around - end");
209+
}
210+
}
211+
212+
@Before("echo()")
213+
void before() {
214+
invocations.add("before");
215+
}
216+
170217
@AfterReturning("echo()")
171-
void succeeded() {
218+
void afterReturning() {
172219
invocations.add("after returning");
173220
}
174221

175222
@AfterThrowing("echo()")
176-
void failed() {
223+
void afterThrowing() {
177224
invocations.add("after throwing");
178225
}
179226

integration-tests/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests-afterFirst.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88
<bean id="echo" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$Echo"/>
99

10-
<bean id="echoAspect" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$EchoAspect" />
10+
<bean id="invocationTrackingAspect" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$InvocationTrackingAspect" />
1111

1212
<aop:config>
13-
<aop:aspect id="echoAdvice" ref="echoAspect">
13+
<aop:aspect id="echoAdvice" ref="invocationTrackingAspect">
1414
<aop:pointcut id="echoMethod" expression="execution(* echo(*))" />
15+
<aop:around method="around" pointcut-ref="echoMethod" />
16+
<aop:before method="before" pointcut-ref="echoMethod" />
1517
<aop:after method="after" pointcut-ref="echoMethod" />
16-
<aop:after-throwing method="failed" pointcut-ref="echoMethod" />
17-
<aop:after-returning method="succeeded" pointcut-ref="echoMethod" />
18+
<aop:after-throwing method="afterThrowing" pointcut-ref="echoMethod" />
19+
<aop:after-returning method="afterReturning" pointcut-ref="echoMethod" />
1820
</aop:aspect>
1921
</aop:config>
2022

integration-tests/src/test/resources/org/springframework/aop/config/AopNamespaceHandlerAdviceOrderIntegrationTests-afterLast.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77

88
<bean id="echo" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$Echo"/>
99

10-
<bean id="echoAspect" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$EchoAspect" />
10+
<bean id="invocationTrackingAspect" class="org.springframework.aop.config.AopNamespaceHandlerAdviceOrderIntegrationTests$InvocationTrackingAspect" />
1111

1212
<aop:config>
13-
<aop:aspect id="echoAdvice" ref="echoAspect">
13+
<aop:aspect id="echoAdvice" ref="invocationTrackingAspect">
1414
<aop:pointcut id="echoMethod" expression="execution(* echo(*))" />
15-
<aop:after-returning method="succeeded" pointcut-ref="echoMethod" />
16-
<aop:after-throwing method="failed" pointcut-ref="echoMethod" />
15+
<aop:around method="around" pointcut-ref="echoMethod" />
16+
<aop:before method="before" pointcut-ref="echoMethod" />
17+
<aop:after-throwing method="afterThrowing" pointcut-ref="echoMethod" />
18+
<aop:after-returning method="afterReturning" pointcut-ref="echoMethod" />
1719
<aop:after method="after" pointcut-ref="echoMethod" />
1820
</aop:aspect>
1921
</aop:config>

spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ void aspectMethodThrowsExceptionLegalOnSignature() {
455455
TestBean target = new TestBean();
456456
UnsupportedOperationException expectedException = new UnsupportedOperationException();
457457
List<Advisor> advisors = getFixture().getAdvisors(
458-
new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException), "someBean"));
458+
new SingletonMetadataAwareAspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
459459
assertThat(advisors.size()).as("One advice method was found").isEqualTo(1);
460460
ITestBean itb = (ITestBean) createProxy(target, advisors, ITestBean.class);
461461
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(
@@ -469,7 +469,7 @@ void aspectMethodThrowsExceptionIllegalOnSignature() {
469469
TestBean target = new TestBean();
470470
RemoteException expectedException = new RemoteException();
471471
List<Advisor> advisors = getFixture().getAdvisors(
472-
new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException), "someBean"));
472+
new SingletonMetadataAwareAspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
473473
assertThat(advisors.size()).as("One advice method was found").isEqualTo(1);
474474
ITestBean itb = (ITestBean) createProxy(target, advisors, ITestBean.class);
475475
assertThatExceptionOfType(UndeclaredThrowableException.class).isThrownBy(
@@ -510,18 +510,18 @@ void twoAdvicesOnOneAspect() {
510510

511511
@Test
512512
void afterAdviceTypes() throws Exception {
513-
ExceptionHandling aspect = new ExceptionHandling();
513+
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
514514
List<Advisor> advisors = getFixture().getAdvisors(
515515
new SingletonMetadataAwareAspectInstanceFactory(aspect, "exceptionHandlingAspect"));
516516
Echo echo = (Echo) createProxy(new Echo(), advisors, Echo.class);
517517

518518
assertThat(aspect.invocations).isEmpty();
519519
assertThat(echo.echo(42)).isEqualTo(42);
520-
assertThat(aspect.invocations).containsExactly("after returning", "after");
520+
assertThat(aspect.invocations).containsExactly("around - start", "before", "after returning", "after", "around - end");
521521

522522
aspect.invocations.clear();
523523
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> echo.echo(new FileNotFoundException()));
524-
assertThat(aspect.invocations).containsExactly("after throwing", "after");
524+
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
525525
}
526526

527527
@Test
@@ -742,11 +742,11 @@ String reverseAdvice(ProceedingJoinPoint pjp, int b, int c, String d, StringBuff
742742

743743

744744
@Aspect
745-
static class ExceptionAspect {
745+
static class ExceptionThrowingAspect {
746746

747747
private final Exception ex;
748748

749-
ExceptionAspect(Exception ex) {
749+
ExceptionThrowingAspect(Exception ex) {
750750
this.ex = ex;
751751
}
752752

@@ -769,7 +769,7 @@ Object echo(Object o) throws Exception {
769769

770770

771771
@Aspect
772-
static class ExceptionHandling {
772+
private static class InvocationTrackingAspect {
773773

774774
List<String> invocations = new ArrayList<>();
775775

@@ -778,13 +778,29 @@ static class ExceptionHandling {
778778
void echo() {
779779
}
780780

781+
@Around("echo()")
782+
Object around(ProceedingJoinPoint joinPoint) throws Throwable {
783+
invocations.add("around - start");
784+
try {
785+
return joinPoint.proceed();
786+
}
787+
finally {
788+
invocations.add("around - end");
789+
}
790+
}
791+
792+
@Before("echo()")
793+
void before() {
794+
invocations.add("before");
795+
}
796+
781797
@AfterReturning("echo()")
782-
void succeeded() {
798+
void afterReturning() {
783799
invocations.add("after returning");
784800
}
785801

786802
@AfterThrowing("echo()")
787-
void failed() {
803+
void afterThrowing() {
788804
invocations.add("after throwing");
789805
}
790806

spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectJPointcutAdvisorTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 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.
@@ -22,6 +22,7 @@
2222
import org.springframework.aop.Pointcut;
2323
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
2424
import org.springframework.aop.aspectj.AspectJExpressionPointcutTests;
25+
import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.ExceptionThrowingAspect;
2526
import org.springframework.aop.framework.AopConfigException;
2627
import org.springframework.beans.testfixture.beans.TestBean;
2728

@@ -44,7 +45,7 @@ public void testSingleton() throws SecurityException, NoSuchMethodException {
4445

4546
InstantiationModelAwarePointcutAdvisorImpl ajpa = new InstantiationModelAwarePointcutAdvisorImpl(
4647
ajexp, TestBean.class.getMethod("getAge"), af,
47-
new SingletonMetadataAwareAspectInstanceFactory(new AbstractAspectJAdvisorFactoryTests.ExceptionAspect(null), "someBean"),
48+
new SingletonMetadataAwareAspectInstanceFactory(new ExceptionThrowingAspect(null), "someBean"),
4849
1, "someBean");
4950

5051
assertThat(ajpa.getAspectMetadata().getPerClausePointcut()).isSameAs(Pointcut.TRUE);

0 commit comments

Comments
 (0)