如何为ControlFlowPointcut构造函数指定几个方法?

问题描述 投票:0回答:2

读一本《临泉》来了个例子

Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test");

很清楚它是如何工作的,但问题是 - 是否可以(以及如何)在构造函数中指出一些方法?我的意思是,如果我想要 1 个切入点适用于 3 个方法 (test1(2,3)),该怎么办?例如:

Pointcut pc = new ControlFlowPointcut(ControlFlowDemo.class, "test, test2, test3");

java spring aop
2个回答
0
投票

你的问题的答案是否定的。

ControlFlowPointcut
不提供您使用多个方法名称来调用它或指定模式。方法名称必须完全匹配,如您在源代码中看到的那样。

然而,你能做的是

  • 切换到原生 AspectJ 并使用现有的
    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是为了讨论核心类是否可以扩展或者更容易扩展以避免重复。


0
投票

从 Spring Framework 6.1 开始,

ControlFlowPointcut
是可扩展的,并为方法名称提供内置模式匹配支持,类似于
NameMatchMethodPointcut
中的模式匹配支持 - 例如:

CustomControlFlowPointcut cflow = new CustomControlFlowPointcut(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();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.