读一本《临泉》来了个例子
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test");
很清楚它是如何工作的,但问题是 - 是否可以(以及如何)在构造函数中指出一些方法?我的意思是,如果我想要 1 个切入点适用于 3 个方法 (test1(2,3)),该怎么办?例如:
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test, test2, test3");
你的问题的答案是否定的。
ControlFlowPointcut
不提供您使用多个方法名称来调用它或指定模式。方法名称必须完全匹配,如您在源代码中看到的那样。
然而,你能做的是
cflow
和 cflowbelow
切入点,或者 protected
或至少具有访问器方法以获得更好的可扩展性的一些成员实际上是 private
,即我最终会复制现有代码。所以我简单地复制并扩展它:package de.scrum_master.spring.q68431056;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
* Pointcut and method matcher for use in simple <b>cflow</b>-style pointcut.
* Note that evaluating such pointcuts is 10-15 times slower than evaluating
* normal pointcuts, but they are useful in some cases.
*
* @author Rod Johnson
* @author Rob Harrop
* @author Juergen Hoeller
* @author Alexander Kriegisch
*/
@SuppressWarnings("serial")
public class MultiMethodControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable {
private Class<?> clazz;
@Nullable
private String methodName;
@Nullable
private Pattern methodPattern;
private volatile int evaluations;
/**
* Construct a new pointcut that matches all control flows below that class.
* @param clazz the clazz
*/
public MultiMethodControlFlowPointcut(Class<?> clazz) {
this(clazz, (String) null);
}
/**
* Construct a new pointcut that matches all calls below the given method
* in the given class. If no method name is given, matches all control flows
* below the given class.
* @param clazz the clazz
* @param methodName the name of the method (may be {@code null})
*/
public MultiMethodControlFlowPointcut(Class<?> clazz, @Nullable String methodName) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
this.methodName = methodName;
}
/**
* Construct a new pointcut that matches all calls below the given method
* in the given class. If no method name is given, matches all control flows
* below the given class.
* @param clazz the clazz
* @param methodPattern regex pattern the name of the method must match with (may be {@code null})
*/
public MultiMethodControlFlowPointcut(Class<?> clazz, Pattern methodPattern) {
this(clazz, (String) null);
this.methodPattern = methodPattern;
}
/**
* Subclasses can override this for greater filtering (and performance).
*/
@Override
public boolean matches(Class<?> clazz) {
return true;
}
/**
* Subclasses can override this if it's possible to filter out some candidate classes.
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
return true;
}
@Override
public boolean isRuntime() {
return true;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
this.evaluations++;
for (StackTraceElement element : new Throwable().getStackTrace()) {
if (
element.getClassName().equals(this.clazz.getName()) &&
(this.methodName == null || element.getMethodName().equals(this.methodName)) &&
(this.methodPattern == null || this.methodPattern.matcher(element.getMethodName()).matches())
) {
//System.out.println("Control flow match: " + element.getClassName() + "." + element.getMethodName());
return true;
}
}
return false;
}
/**
* It's useful to know how many times we've fired, for optimization.
*/
public int getEvaluations() {
return this.evaluations;
}
@Override
public ClassFilter getClassFilter() {
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MultiMethodControlFlowPointcut)) {
return false;
}
MultiMethodControlFlowPointcut that = (MultiMethodControlFlowPointcut) other;
return (this.clazz.equals(that.clazz)) &&
ObjectUtils.nullSafeEquals(this.methodName, that.methodName) &&
ObjectUtils.nullSafeEquals(this.methodPattern, that.methodPattern);
}
@Override
public int hashCode() {
int result = clazz.hashCode();
result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
result = 31 * result + (methodPattern != null ? methodPattern.hashCode() : 0);
return result;
}
}
除了以下部分之外,大部分代码与原始代码相同:
@Nullable private Pattern methodPattern
public MultiMethodControlFlowPointcut(Class<?>, Pattern)
equals(..)
和 hashCode()
考虑 methodPattern
public boolean matches(Method, Class<?>, Object...)
考虑methodPattern
所以如果你现在用
实例化这个类new MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test.*")
)
或
new MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test[1-3]")
)
它应该完全符合你的要求。
实施注意事项:
而不是新的
Pattern
字段 + 构造函数,我可以简单地将现有 String
字段默认视为正则表达式模式,但尽管向后兼容,但这会减慢精确方法名称匹配的速度。也许这可以忽略不计,我没有测量。
正则表达式语法与 AspectJ 或 Spring AOP 语法不一致,后者只有简单的
*
模式,而不是成熟的正则表达式模式。但是如果您使用自己的自定义切入点类,您也可以使用更强大的东西。
当然,可以很容易地扩展实现,以允许对
Class
部分进行模式或子类匹配,而不仅仅是 Method
部分。但这也会进一步减慢切入点匹配速度。
如果您想查看控制流中的哪个方法触发了通知执行,请取消注释方法
matches(Method, Class<?>, Object...)
中的日志语句。
更新:我创建了Spring问题#27187是为了讨论核心类是否可以扩展或者更容易扩展以避免重复。
从 Spring Framework 6.1 开始,
ControlFlowPointcut
是可扩展的,并为方法名称提供内置模式匹配支持,类似于 NameMatchMethodPointcut
中的模式匹配支持 - 例如:
ControlFlowPointcut cflow = new ControlFlowPointcut(MyComponent.class, "set*", "getAge");
如果您想切换到正则表达式,现在只需实现以下内容并使用组件类型和正则表达式实例化即可,如下所示:
ControlFlowPointcut cflow = new RegExControlFlowPointcut(MyComponent.class, "(set.*?|getAge)")
.
public 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();
}
}