Skip to content

Commit 0c65a5e

Browse files
committed
Accept ajc-compiled @aspect classes for Spring AOP proxy usage
AspectJExpressionPointcut leniently ignores unsupported expression. Closes gh-32793
1 parent ca9eb57 commit 0c65a5e

File tree

3 files changed

+52
-80
lines changed

3 files changed

+52
-80
lines changed

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

+15-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -174,25 +174,30 @@ public void setBeanFactory(BeanFactory beanFactory) {
174174

175175
@Override
176176
public ClassFilter getClassFilter() {
177-
obtainPointcutExpression();
177+
checkExpression();
178178
return this;
179179
}
180180

181181
@Override
182182
public MethodMatcher getMethodMatcher() {
183-
obtainPointcutExpression();
183+
checkExpression();
184184
return this;
185185
}
186186

187187

188188
/**
189-
* Check whether this pointcut is ready to match,
190-
* lazily building the underlying AspectJ pointcut expression.
189+
* Check whether this pointcut is ready to match.
191190
*/
192-
private PointcutExpression obtainPointcutExpression() {
191+
private void checkExpression() {
193192
if (getExpression() == null) {
194193
throw new IllegalStateException("Must set property 'expression' before attempting to match");
195194
}
195+
}
196+
197+
/**
198+
* Lazily build the underlying AspectJ pointcut expression.
199+
*/
200+
private PointcutExpression obtainPointcutExpression() {
196201
if (this.pointcutExpression == null) {
197202
this.pointcutClassLoader = determinePointcutClassLoader();
198203
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -269,10 +274,9 @@ public PointcutExpression getPointcutExpression() {
269274

270275
@Override
271276
public boolean matches(Class<?> targetClass) {
272-
PointcutExpression pointcutExpression = obtainPointcutExpression();
273277
try {
274278
try {
275-
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
279+
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
276280
}
277281
catch (ReflectionWorldException ex) {
278282
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -283,6 +287,9 @@ public boolean matches(Class<?> targetClass) {
283287
}
284288
}
285289
}
290+
catch (IllegalArgumentException | IllegalStateException ex) {
291+
throw ex;
292+
}
286293
catch (Throwable ex) {
287294
logger.debug("PointcutExpression matching rejected target class", ex);
288295
}
@@ -291,7 +298,6 @@ public boolean matches(Class<?> targetClass) {
291298

292299
@Override
293300
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
294-
obtainPointcutExpression();
295301
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
296302

297303
// Special handling for this, target, @this, @target, @annotation
@@ -329,7 +335,6 @@ public boolean isRuntime() {
329335

330336
@Override
331337
public boolean matches(Method method, Class<?> targetClass, Object... args) {
332-
obtainPointcutExpression();
333338
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
334339

335340
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,

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

+3-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -18,7 +18,6 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Constructor;
21-
import java.lang.reflect.Field;
2221
import java.lang.reflect.Method;
2322
import java.lang.reflect.Modifier;
2423
import java.util.HashMap;
@@ -57,8 +56,6 @@
5756
*/
5857
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
5958

60-
private static final String AJC_MAGIC = "ajc$";
61-
6259
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
6360
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
6461

@@ -69,37 +66,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
6966
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
7067

7168

72-
/**
73-
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
74-
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
75-
* is that aspects written in the code-style (AspectJ language) also have the annotation present
76-
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
77-
*/
7869
@Override
7970
public boolean isAspect(Class<?> clazz) {
80-
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
81-
}
82-
83-
private boolean hasAspectAnnotation(Class<?> clazz) {
8471
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
8572
}
8673

87-
/**
88-
* We need to detect this as "code-style" AspectJ aspects should not be
89-
* interpreted by Spring AOP.
90-
*/
91-
private boolean compiledByAjc(Class<?> clazz) {
92-
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
93-
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
94-
// an implementation detail of the AspectJ compiler.
95-
for (Field field : clazz.getDeclaredFields()) {
96-
if (field.getName().startsWith(AJC_MAGIC)) {
97-
return true;
98-
}
99-
}
100-
return false;
101-
}
102-
10374
@Override
10475
public void validate(Class<?> aspectClass) throws AopConfigException {
10576
// If the parent has the annotation and isn't abstract it's an error
@@ -124,6 +95,7 @@ public void validate(Class<?> aspectClass) throws AopConfigException {
12495
}
12596
}
12697

98+
12799
/**
128100
* Find and return the first AspectJ annotation on the given method
129101
* (there <i>should</i> only be one anyway...).
@@ -163,7 +135,7 @@ protected enum AspectJAnnotationType {
163135

164136

165137
/**
166-
* Class modelling an AspectJ annotation, exposing its type enumeration and
138+
* Class modeling an AspectJ annotation, exposing its type enumeration and
167139
* pointcut String.
168140
* @param <A> the annotation type
169141
*/

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

+34-39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -23,9 +23,6 @@
2323

2424
import org.aopalliance.intercept.MethodInterceptor;
2525
import org.aopalliance.intercept.MethodInvocation;
26-
import org.aspectj.weaver.tools.PointcutExpression;
27-
import org.aspectj.weaver.tools.PointcutPrimitive;
28-
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
2926
import org.junit.jupiter.api.BeforeEach;
3027
import org.junit.jupiter.api.Test;
3128
import test.annotation.EmptySpringAnnotation;
@@ -42,7 +39,6 @@
4239
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
4340

4441
import static org.assertj.core.api.Assertions.assertThat;
45-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4642
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4743
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4844

@@ -65,7 +61,7 @@ public class AspectJExpressionPointcutTests {
6561

6662

6763
@BeforeEach
68-
public void setUp() throws NoSuchMethodException {
64+
public void setup() throws NoSuchMethodException {
6965
getAge = TestBean.class.getMethod("getAge");
7066
setAge = TestBean.class.getMethod("setAge", int.class);
7167
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -175,25 +171,25 @@ private void testWithinPackage(boolean matchSubpackages) throws SecurityExceptio
175171
@Test
176172
public void testFriendlyErrorOnNoLocationClassMatching() {
177173
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
178-
assertThatIllegalStateException().isThrownBy(() ->
179-
pc.matches(ITestBean.class))
180-
.withMessageContaining("expression");
174+
assertThatIllegalStateException()
175+
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
176+
.withMessageContaining("expression");
181177
}
182178

183179
@Test
184180
public void testFriendlyErrorOnNoLocation2ArgMatching() {
185181
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
186-
assertThatIllegalStateException().isThrownBy(() ->
187-
pc.matches(getAge, ITestBean.class))
188-
.withMessageContaining("expression");
182+
assertThatIllegalStateException()
183+
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
184+
.withMessageContaining("expression");
189185
}
190186

191187
@Test
192188
public void testFriendlyErrorOnNoLocation3ArgMatching() {
193189
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
194-
assertThatIllegalStateException().isThrownBy(() ->
195-
pc.matches(getAge, ITestBean.class, (Object[]) null))
196-
.withMessageContaining("expression");
190+
assertThatIllegalStateException()
191+
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
192+
.withMessageContaining("expression");
197193
}
198194

199195

@@ -210,8 +206,10 @@ public void testMatchWithArgs() throws Exception {
210206
// not currently testable in a reliable fashion
211207
//assertDoesNotMatchStringClass(classFilter);
212208

213-
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
214-
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11)).as("Should not match setSomeNumber with Integer input").isFalse();
209+
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D))
210+
.as("Should match with setSomeNumber with Double input").isTrue();
211+
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11))
212+
.as("Should not match setSomeNumber with Integer input").isFalse();
215213
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
216214
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
217215
}
@@ -246,14 +244,13 @@ public void testDynamicMatchingProxy() {
246244
@Test
247245
public void testInvalidExpression() {
248246
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
249-
assertThatIllegalArgumentException().isThrownBy(
250-
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
247+
assertThatIllegalArgumentException().isThrownBy(() -> getPointcut(expression).getClassFilter().matches(Object.class));
251248
}
252249

253250
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
254251
TestBean target = new TestBean();
255252

256-
Pointcut pointcut = getPointcut(pointcutExpression);
253+
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
257254

258255
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
259256
advisor.setAdvice(interceptor);
@@ -277,40 +274,29 @@ private void assertMatchesTestBeanClass(ClassFilter classFilter) {
277274
@Test
278275
public void testWithUnsupportedPointcutPrimitive() {
279276
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
280-
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
281-
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
282-
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
277+
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
283278
}
284279

285280
@Test
286281
public void testAndSubstitution() {
287-
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
288-
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
289-
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
282+
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
283+
String expr = pc.getPointcutExpression().getPointcutExpression();
284+
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
290285
}
291286

292287
@Test
293288
public void testMultipleAndSubstitutions() {
294-
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
295-
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
296-
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
289+
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
290+
String expr = pc.getPointcutExpression().getPointcutExpression();
291+
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
297292
}
298293

299-
private Pointcut getPointcut(String expression) {
294+
private AspectJExpressionPointcut getPointcut(String expression) {
300295
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
301296
pointcut.setExpression(expression);
302297
return pointcut;
303298
}
304299

305-
306-
public static class OtherIOther implements IOther {
307-
308-
@Override
309-
public void absquatulate() {
310-
// Empty
311-
}
312-
}
313-
314300
@Test
315301
public void testMatchGenericArgument() {
316302
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
@@ -505,6 +491,15 @@ public void testAnnotationOnMethodArgumentsWithWildcards() throws Exception {
505491
}
506492

507493

494+
public static class OtherIOther implements IOther {
495+
496+
@Override
497+
public void absquatulate() {
498+
// Empty
499+
}
500+
}
501+
502+
508503
public static class HasGeneric {
509504

510505
public void setFriends(List<TestBean> friends) {

0 commit comments

Comments
 (0)