Skip to content

Commit e81c788

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

File tree

3 files changed

+31
-57
lines changed

3 files changed

+31
-57
lines changed

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

+14-9
Original file line numberDiff line numberDiff line change
@@ -169,25 +169,30 @@ public void setBeanFactory(BeanFactory beanFactory) {
169169

170170
@Override
171171
public ClassFilter getClassFilter() {
172-
obtainPointcutExpression();
172+
checkExpression();
173173
return this;
174174
}
175175

176176
@Override
177177
public MethodMatcher getMethodMatcher() {
178-
obtainPointcutExpression();
178+
checkExpression();
179179
return this;
180180
}
181181

182182

183183
/**
184-
* Check whether this pointcut is ready to match,
185-
* lazily building the underlying AspectJ pointcut expression.
184+
* Check whether this pointcut is ready to match.
186185
*/
187-
private PointcutExpression obtainPointcutExpression() {
186+
private void checkExpression() {
188187
if (getExpression() == null) {
189188
throw new IllegalStateException("Must set property 'expression' before attempting to match");
190189
}
190+
}
191+
192+
/**
193+
* Lazily build the underlying AspectJ pointcut expression.
194+
*/
195+
private PointcutExpression obtainPointcutExpression() {
191196
if (this.pointcutExpression == null) {
192197
this.pointcutClassLoader = determinePointcutClassLoader();
193198
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -264,10 +269,9 @@ public PointcutExpression getPointcutExpression() {
264269

265270
@Override
266271
public boolean matches(Class<?> targetClass) {
267-
PointcutExpression pointcutExpression = obtainPointcutExpression();
268272
try {
269273
try {
270-
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
274+
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
271275
}
272276
catch (ReflectionWorldException ex) {
273277
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -278,6 +282,9 @@ public boolean matches(Class<?> targetClass) {
278282
}
279283
}
280284
}
285+
catch (IllegalArgumentException | IllegalStateException ex) {
286+
throw ex;
287+
}
281288
catch (Throwable ex) {
282289
logger.debug("PointcutExpression matching rejected target class", ex);
283290
}
@@ -286,7 +293,6 @@ public boolean matches(Class<?> targetClass) {
286293

287294
@Override
288295
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
289-
obtainPointcutExpression();
290296
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
291297

292298
// Special handling for this, target, @this, @target, @annotation
@@ -324,7 +330,6 @@ public boolean isRuntime() {
324330

325331
@Override
326332
public boolean matches(Method method, Class<?> targetClass, Object... args) {
327-
obtainPointcutExpression();
328333
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
329334

330335
// 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

+2-30
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.
@@ -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.util.Map;
2423
import java.util.StringTokenizer;
@@ -56,8 +55,6 @@
5655
*/
5756
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
5857

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

@@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
6865
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
6966

7067

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

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

89+
11890
/**
11991
* Find and return the first AspectJ annotation on the given method
12092
* (there <i>should</i> only be one anyway...).

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

+15-18
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
import org.aopalliance.intercept.MethodInterceptor;
2525
import org.aopalliance.intercept.MethodInvocation;
26-
import org.aspectj.weaver.tools.PointcutPrimitive;
27-
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
2826
import org.junit.jupiter.api.BeforeEach;
2927
import org.junit.jupiter.api.Test;
3028
import test.annotation.EmptySpringAnnotation;
@@ -41,7 +39,6 @@
4139
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
4240

4341
import static org.assertj.core.api.Assertions.assertThat;
44-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4542
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4643
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4744

@@ -174,25 +171,25 @@ private void testWithinPackage(boolean matchSubpackages) throws SecurityExceptio
174171
@Test
175172
public void testFriendlyErrorOnNoLocationClassMatching() {
176173
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
177-
assertThatIllegalStateException().isThrownBy(() ->
178-
pc.matches(ITestBean.class))
179-
.withMessageContaining("expression");
174+
assertThatIllegalStateException()
175+
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
176+
.withMessageContaining("expression");
180177
}
181178

182179
@Test
183180
public void testFriendlyErrorOnNoLocation2ArgMatching() {
184181
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
185-
assertThatIllegalStateException().isThrownBy(() ->
186-
pc.matches(getAge, ITestBean.class))
187-
.withMessageContaining("expression");
182+
assertThatIllegalStateException()
183+
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
184+
.withMessageContaining("expression");
188185
}
189186

190187
@Test
191188
public void testFriendlyErrorOnNoLocation3ArgMatching() {
192189
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
193-
assertThatIllegalStateException().isThrownBy(() ->
194-
pc.matches(getAge, ITestBean.class, (Object[]) null))
195-
.withMessageContaining("expression");
190+
assertThatIllegalStateException()
191+
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
192+
.withMessageContaining("expression");
196193
}
197194

198195

@@ -209,8 +206,10 @@ public void testMatchWithArgs() throws Exception {
209206
// not currently testable in a reliable fashion
210207
//assertDoesNotMatchStringClass(classFilter);
211208

212-
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
213-
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();
214213
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
215214
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
216215
}
@@ -245,7 +244,7 @@ public void testDynamicMatchingProxy() {
245244
@Test
246245
public void testInvalidExpression() {
247246
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
248-
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
247+
assertThatIllegalArgumentException().isThrownBy(() -> getPointcut(expression).getClassFilter().matches(Object.class));
249248
}
250249

251250
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
@@ -275,9 +274,7 @@ private void assertMatchesTestBeanClass(ClassFilter classFilter) {
275274
@Test
276275
public void testWithUnsupportedPointcutPrimitive() {
277276
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
278-
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
279-
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
280-
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
277+
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
281278
}
282279

283280
@Test

0 commit comments

Comments
 (0)