如何为 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 并在那里使用现有的 cflowcflowbelow 切入点,或者
  • 复制和修改源代码。我很好奇,所以我为你做了那个。一开始我试图扩展它,但是一些成员应该是 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 是否可以扩展或更容易扩展以避免重复。