From 5c1cdcb245641585682661b803bca2204e064aa4 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 25 Oct 2023 14:53:07 +0200 Subject: [PATCH] Improve ControlFlowPointcut extensibility This commit makes ControlFlowPointcut more open to subclasses by: 1. Making the ControlFlowPointcut#clazz field protected. 2. Making the ControlFlowPointcut#methodName field protected. 3. Introducing a protected incrementEvaluationCount() method. Closes gh-27187 --- .../aop/support/ControlFlowPointcut.java | 23 ++++++-- .../aop/support/ControlFlowPointcutTests.java | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java index 3bb0cc58040..cb2b368fd26 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java @@ -41,10 +41,18 @@ import org.springframework.util.ObjectUtils; @SuppressWarnings("serial") public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable { - private final Class clazz; + /** + * The class against which to match. + *

Available for use in subclasses since 6.1. + */ + protected final Class clazz; + /** + * The method against which to match, potentially {@code null}. + *

Available for use in subclasses since 6.1. + */ @Nullable - private final String methodName; + protected final String methodName; private final AtomicInteger evaluationCount = new AtomicInteger(); @@ -97,7 +105,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher @Override public boolean matches(Method method, Class targetClass, Object... args) { - this.evaluationCount.incrementAndGet(); + incrementEvaluationCount(); for (StackTraceElement element : new Throwable().getStackTrace()) { if (element.getClassName().equals(this.clazz.getName()) && @@ -117,6 +125,15 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher return this.evaluationCount.get(); } + /** + * Increment the {@link #getEvaluations() evaluation count}. + * @since 6.1 + * @see #matches(Method, Class, Object...) + */ + protected final void incrementEvaluationCount() { + this.evaluationCount.incrementAndGet(); + } + @Override public ClassFilter getClassFilter() { diff --git a/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java b/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java index 32fc6c98e45..e6b006e30cc 100644 --- a/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/support/ControlFlowPointcutTests.java @@ -16,6 +16,8 @@ package org.springframework.aop.support; +import java.lang.reflect.Method; + import org.junit.jupiter.api.Test; import org.springframework.aop.Pointcut; @@ -31,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Rod Johnson * @author Chris Beams + * @author Sam Brannen */ class ControlFlowPointcutTests { @@ -60,6 +63,57 @@ class ControlFlowPointcutTests { assertThat(cflow.getEvaluations()).isEqualTo(3); } + @Test + void controlFlowPointcutIsExtensible() { + @SuppressWarnings("serial") + class CustomControlFlowPointcut extends ControlFlowPointcut { + + CustomControlFlowPointcut(Class clazz, String methodName) { + super(clazz, methodName); + } + + @Override + public boolean matches(Method method, Class targetClass, Object... args) { + super.incrementEvaluationCount(); + return super.matches(method, targetClass, args); + } + + Class trackedClass() { + return super.clazz; + } + + String trackedMethod() { + return super.methodName; + } + } + + CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(One.class, "getAge"); + + assertThat(cflow.trackedClass()).isEqualTo(One.class); + assertThat(cflow.trackedMethod()).isEqualTo("getAge"); + + TestBean target = new TestBean("Jane", 27); + ProxyFactory pf = new ProxyFactory(target); + NopInterceptor nop = new NopInterceptor(); + pf.addAdvisor(new DefaultPointcutAdvisor(cflow, nop)); + ITestBean proxy = (ITestBean) pf.getProxy(); + + // Not advised: the proxy is not invoked under One#getAge + assertThat(proxy.getAge()).isEqualTo(target.getAge()); + assertThat(nop.getCount()).isEqualTo(0); + assertThat(cflow.getEvaluations()).isEqualTo(2); // intentional double increment + + // Will be advised: the proxy is invoked under One#getAge + assertThat(new One().getAge(proxy)).isEqualTo(target.getAge()); + assertThat(nop.getCount()).isEqualTo(1); + assertThat(cflow.getEvaluations()).isEqualTo(4); // intentional double increment + + // Won't be advised: the proxy is not invoked under One#getAge + assertThat(new One().nomatch(proxy)).isEqualTo(target.getAge()); + assertThat(nop.getCount()).isEqualTo(1); + assertThat(cflow.getEvaluations()).isEqualTo(6); // intentional double increment + } + /** * Check that we can use a cflow pointcut only in conjunction with * a static pointcut: e.g. all setter methods that are invoked under