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
This commit is contained in:
Sam Brannen 2023-10-28 15:53:20 +02:00
parent 7f1beb0140
commit c7bb981db1
2 changed files with 53 additions and 3 deletions

View File

@ -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.
* <p>This method is invoked by {@link #matches(Method, Class, Object...)}.
* <p>The default implementation retrieves the method name pattern from
* {@link #methodNamePatterns} and delegates to {@link #isMatch(String, String)}.
* <p>Can be overridden in subclasses &mdash; 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.
* <p>This method is invoked by {@link #isMatch(String, int)}.
* <p>The default implementation checks for direct equality as well as
* {@code xxx*}, {@code *xxx}, {@code *xxx*}, and {@code xxx*yyy} matches.
* <p>Can be overridden in subclasses &mdash; 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) {

View File

@ -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<Pattern> 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();
}
}
}