如何为 ControlFlowPointcut 构造函数指定几个方法?
How to specify a few methods for ControlFlowPointcut constructor?
看了一本《临Spring》的书,得出了一个例子
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test");
很清楚它是如何工作的,但问题是 - 是否可以(以及如何)指出构造函数中的一些方法?我的意思是,如果我想要 1 个适用于 3 种方法的切入点 (test1(2,3))。比如像:
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test, test2, test3");
你的问题的答案是否定的。 ControlFlowPointcut
不提供使用多个方法名称调用它或指定模式的方法。方法名称必须完全匹配,如您在 source code.
中所见
然而,你能做的是
- 切换到本机 AspectJ 并在那里使用现有的
cflow
和 cflowbelow
切入点,或者
- 复制和修改源代码。我很好奇,所以我为你做了那个。一开始我试图扩展它,但是一些成员应该是
protected
在 class 中或者至少有访问器方法以获得更好的可扩展性,实际上是 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
所以如果现在你用
实例化这个class
new MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test.*")
)
或
new MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test[1-3]")
)
它应该完全符合您的要求。
实现说明:
而不是新的 Pattern
字段 + 构造函数,我可以简单地将现有的 String
字段默认视为正则表达式模式,但尽管向后兼容减慢精确的方法名称匹配。可能是微不足道的,我没测过。
正则表达式语法与 AspectJ 或 Spring AOP 语法不一致,后者只有简单的 *
模式,而不是成熟的正则表达式。但是,如果您使用自己的自定义切入点 class,您也可以使用更强大的东西。
当然,可以很容易地扩展实现以允许 Class
部分的模式或 subclass 匹配,而不仅仅是 Method
一。但这也会进一步减慢切入点匹配。
取消注释方法 matches(Method, Class<?>, Object...)
中的日志语句,如果您想查看控制流中的哪个方法触发了建议执行。
更新: 我创建了 Spring issue #27187 以讨论核心 class 是否可以扩展或更容易扩展以避免重复。
看了一本《临Spring》的书,得出了一个例子
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test");
很清楚它是如何工作的,但问题是 - 是否可以(以及如何)指出构造函数中的一些方法?我的意思是,如果我想要 1 个适用于 3 种方法的切入点 (test1(2,3))。比如像:
Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test, test2, test3");
你的问题的答案是否定的。 ControlFlowPointcut
不提供使用多个方法名称调用它或指定模式的方法。方法名称必须完全匹配,如您在 source code.
然而,你能做的是
- 切换到本机 AspectJ 并在那里使用现有的
cflow
和cflowbelow
切入点,或者 - 复制和修改源代码。我很好奇,所以我为你做了那个。一开始我试图扩展它,但是一些成员应该是
protected
在 class 中或者至少有访问器方法以获得更好的可扩展性,实际上是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
所以如果现在你用
实例化这个classnew MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test.*")
)
或
new MultiMethodControlFlowPointcut(
ControlFlowDemo.class, Pattern.compile("test[1-3]")
)
它应该完全符合您的要求。
实现说明:
而不是新的
Pattern
字段 + 构造函数,我可以简单地将现有的String
字段默认视为正则表达式模式,但尽管向后兼容减慢精确的方法名称匹配。可能是微不足道的,我没测过。正则表达式语法与 AspectJ 或 Spring AOP 语法不一致,后者只有简单的
*
模式,而不是成熟的正则表达式。但是,如果您使用自己的自定义切入点 class,您也可以使用更强大的东西。当然,可以很容易地扩展实现以允许
Class
部分的模式或 subclass 匹配,而不仅仅是Method
一。但这也会进一步减慢切入点匹配。取消注释方法
matches(Method, Class<?>, Object...)
中的日志语句,如果您想查看控制流中的哪个方法触发了建议执行。
更新: 我创建了 Spring issue #27187 以讨论核心 class 是否可以扩展或更容易扩展以避免重复。