From c7bb981db15170d3941251d7fcc212dbed1b9151 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 28 Oct 2023 15:53:20 +0200 Subject: [PATCH] Ensure it's possible to extend ControlFlowPointcut with RegEx support Commit d3fba6d49b introduced built-in pattern matching support for method names in ControlFlowPointcut; however, it was still cumbersome to extend ControlFlowPointcut with support for regular expressions instead of simple pattern matching. To address that, this commit introduces a variant of isMatch() that accepts the pattern index instead of the pre-resolved method name pattern. The default implementation retrieves the method name pattern from the methodNamePatterns field and delegates to isMatch(String, String). Subclasses can override the new isMatch(String, int) method to support regular expressions, as can be seen in the example RegExControlFlowPointcut class in ControlFlowPointcutTests. See gh-31435 --- .../aop/support/ControlFlowPointcut.java | 30 +++++++++++++++++-- .../aop/support/ControlFlowPointcutTests.java | 26 ++++++++++++++++ 2 files changed, 53 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 2b4242a3344..e04714ed792 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 @@ -151,8 +151,9 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher if (this.methodNamePatterns.isEmpty()) { return true; } - for (String methodNamePattern : this.methodNamePatterns) { - if (isMatch(element.getMethodName(), methodNamePattern)) { + String methodName = element.getMethodName(); + for (int i = 0; i < this.methodNamePatterns.size(); i++) { + if (isMatch(methodName, i)) { return true; } } @@ -179,8 +180,31 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher this.evaluationCount.incrementAndGet(); } + /** + * Determine if the given method name matches the method name pattern at the + * specified index. + *

This method is invoked by {@link #matches(Method, Class, Object...)}. + *

The default implementation retrieves the method name pattern from + * {@link #methodNamePatterns} and delegates to {@link #isMatch(String, String)}. + *

Can be overridden in subclasses — for example, to support + * regular expressions. + * @param methodName the method name to check + * @param patternIndex the index of the method name pattern + * @return {@code true} if the method name matches the pattern at the specified + * index + * @since 6.1 + * @see #methodNamePatterns + * @see #isMatch(String, String) + * @see #matches(Method, Class, Object...) + */ + protected boolean isMatch(String methodName, int patternIndex) { + String methodNamePattern = this.methodNamePatterns.get(patternIndex); + return isMatch(methodName, methodNamePattern); + } + /** * Determine if the given method name matches the method name pattern. + *

This method is invoked by {@link #isMatch(String, int)}. *

The default implementation checks for direct equality as well as * {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} matches. *

Can be overridden in subclasses — for example, to support a @@ -189,7 +213,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher * @param methodNamePattern the method name pattern * @return {@code true} if the method name matches the pattern * @since 6.1 - * @see #matches(Method, Class, Object...) + * @see #isMatch(String, int) * @see PatternMatchUtils#simpleMatch(String, String) */ protected boolean isMatch(String methodName, String methodNamePattern) { 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 4165fe62a2c..3401b6fa18b 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 @@ -18,6 +18,7 @@ package org.springframework.aop.support; import java.lang.reflect.Method; import java.util.List; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; @@ -73,6 +74,15 @@ class ControlFlowPointcutTests { assertMatchesSetAndGetAge(cflow); } + @Test + void regExControlFlowPointcut() { + ControlFlowPointcut cflow = new RegExControlFlowPointcut(MyComponent.class, "(set.*?|getAge)"); + assertMatchesSetAndGetAge(cflow); + + cflow = new RegExControlFlowPointcut(MyComponent.class, "set", "^getAge$"); + assertMatchesSetAndGetAge(cflow); + } + @Test void controlFlowPointcutIsExtensible() { CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(MyComponent.class, "set*", "getAge", "set*", "set*"); @@ -249,4 +259,20 @@ class ControlFlowPointcutTests { } } + @SuppressWarnings("serial") + private static class RegExControlFlowPointcut extends ControlFlowPointcut { + + private final List compiledPatterns; + + RegExControlFlowPointcut(Class clazz, String... methodNamePatterns) { + super(clazz, methodNamePatterns); + this.compiledPatterns = super.methodNamePatterns.stream().map(Pattern::compile).toList(); + } + + @Override + protected boolean isMatch(String methodName, int patternIndex) { + return this.compiledPatterns.get(patternIndex).matcher(methodName).matches(); + } + } + }