Browse Source

Accept ajc-compiled @Aspect classes for Spring AOP proxy usage

AspectJExpressionPointcut leniently ignores unsupported expression.

Closes gh-32793
pull/33048/head
Juergen Hoeller 2 years ago
parent
commit
e81c788274
  1. 23
      spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java
  2. 32
      spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
  3. 33
      spring-aop/src/test/java/org/springframework/aop/aspectj/AspectJExpressionPointcutTests.java

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

@ -169,25 +169,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @@ -169,25 +169,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
checkExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
checkExpression();
return this;
}
/**
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
* Check whether this pointcut is ready to match.
*/
private PointcutExpression obtainPointcutExpression() {
private void checkExpression() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
}
/**
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@ -264,10 +269,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @@ -264,10 +269,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
try {
try {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@ -278,6 +282,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @@ -278,6 +282,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
}
catch (IllegalArgumentException | IllegalStateException ex) {
throw ex;
}
catch (Throwable ex) {
logger.debug("PointcutExpression matching rejected target class", ex);
}
@ -286,7 +293,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @@ -286,7 +293,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
@ -324,7 +330,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut @@ -324,7 +330,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation; @@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.StringTokenizer;
@ -56,8 +55,6 @@ import org.springframework.lang.Nullable; @@ -56,8 +55,6 @@ import org.springframework.lang.Nullable;
*/
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
private static final String AJC_MAGIC = "ajc$";
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac @@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
/**
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
* is that aspects written in the code-style (AspectJ language) also have the annotation present
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
*/
@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
/**
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
private boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac @@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
/**
* Find and return the first AspectJ annotation on the given method
* (there <i>should</i> only be one anyway...).

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

@ -23,8 +23,6 @@ import java.util.Map; @@ -23,8 +23,6 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import test.annotation.EmptySpringAnnotation;
@ -41,7 +39,6 @@ import org.springframework.beans.testfixture.beans.TestBean; @@ -41,7 +39,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@ -174,25 +171,25 @@ public class AspectJExpressionPointcutTests { @@ -174,25 +171,25 @@ public class AspectJExpressionPointcutTests {
@Test
public void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@ -209,8 +206,10 @@ public class AspectJExpressionPointcutTests { @@ -209,8 +206,10 @@ public class AspectJExpressionPointcutTests {
// not currently testable in a reliable fashion
//assertDoesNotMatchStringClass(classFilter);
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11)).as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D))
.as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11))
.as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
}
@ -245,7 +244,7 @@ public class AspectJExpressionPointcutTests { @@ -245,7 +244,7 @@ public class AspectJExpressionPointcutTests {
@Test
public void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
assertThatIllegalArgumentException().isThrownBy(() -> getPointcut(expression).getClassFilter().matches(Object.class));
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
@ -275,9 +274,7 @@ public class AspectJExpressionPointcutTests { @@ -275,9 +274,7 @@ public class AspectJExpressionPointcutTests {
@Test
public void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
@Test

Loading…
Cancel
Save