是否有可能在与字段集切入点相关的建议中获得方法的注释?

Is it possible to get annotations of a method in advice related with field set pointcuts?

我有一个方面,其建议在设置字段时执行。我想获取执行建议的方法的注释对象(=设置字段的位置)。可能吗?

public class WovenClass {

  private int value;

  @Validated({Group1.class, Group2.class})
  public void setField(int value) {
    this.value= value; // Advice should weave here and I want pass '@Validated' annotation to Advice.
  }
}

@Aspect
public class MyAspect {

  @AfterReturning("set(* *)")
  public void intercept(JoinPoint jp) {
    // I want to handle '@Validated' annotation here. 
  }
}

并且还想知道所有字段集的切入点表达式是否正确。很抱歉提出了多个问题。

基于setter 方法的验证

目前,您正在使用 set poitcut 拦截字段写访问。如果要从 setter 方法访问注解,则需要使用拦截方法的切入点,例如 execution and/or @annotation。然后就很容易访问注解了。

字段写访问权限 this.value= value 本身没有注释,也不能注释,因为 JVM 不知道单独代码行上的注释。您当然可以注释该字段,然后从该字段中获取注释,但我认为带注释的 setter 方法是一种很好的方法。就你提供的一点点信息,这很难说。

顺便说一句,set 切入点还会拦截来自注释方法以外的地方的字段写入访问,这似乎不是您想要的。

您的代码中的另一个问题是,在 @AfterReturning 通知中,您无法阻止要验证的值被分配,因为正如切入点名称所暗示的那样,方面在 之后触发 这些方法已经完成了它的工作。如果你想在否定验证的情况下抛出验证异常,你应该使用 @Before 切入点,或者如果你想做一些更复杂的事情,比如在中设置默认值,则应该使用 @Around 切入点否定验证的情况。我在 MCVE:

中向您展示了一个简单的案例

Helper classes 编译示例代码:

package de.scrum_master.app;

public class Group1 {}
package de.scrum_master.app;

public class Group2 {}
package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Validated {
  Class<?>[] value();
}

目标 class 与 setter 待验证,主要方法:

package de.scrum_master.app;

public class WovenClass {
  private int value;
  private String name;

  public void setName(String name) {
    this.name = name;
  }

  @Validated({ Group1.class, Group2.class })
  public void setField(int value) {
    this.value= value;
  }

  public static void main(String[] args) {
    // Should not be intercepted (no validation annotation)
    new WovenClass().setName("foo");
    // Should yield positive validation
    new WovenClass().setField(11);
    // Should yield negative validation -> exception
    new WovenClass().setField(0);
  }
}

验证方面:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import de.scrum_master.app.Validated;

@Aspect
public class ValidationAspect {
  @Before("execution(* set*(*)) && @annotation(validated) && args(value)")
  public void validate(JoinPoint jp, Object value, Validated validated) {
    System.out.println(jp);
    System.out.println("  validation rule: " + validated);
    System.out.println("  value to be set: " + value);
    // Some random condition in order to mimic a negative validation
    if (value.equals(0))
      throw new RuntimeException("field validation failed");
  }
}

请注意切入点的组成部分以及注解和方法参数如何方便地绑定到建议方法值,您可以直接访问这些方法值,而无需任何注解访问反射魔法或像 jp.getArgs[0] 这样丑陋的调用.

控制台日志:

execution(void de.scrum_master.app.WovenClass.setField(int))
  validation rule: @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  value to be set: 11
execution(void de.scrum_master.app.WovenClass.setField(int))
  validation rule: @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  value to be set: 0
Exception in thread "main" java.lang.RuntimeException: field validation failed
    at de.scrum_master.aspect.ValidationAspect.validate(ValidationAspect.aj:18)
    at de.scrum_master.app.WovenClass.setField(WovenClass.java:13)
    at de.scrum_master.app.WovenClass.main(WovenClass.java:22)

基于字段写入权限的验证

更新: 好的,现在我们已经确定无论出于何种原因你想在任何地方拦截字段 setters 而不仅仅是在注释 setter 中方法,使用 EnclosingStaticPart 建议参数以获得调用方法?但是因为有些方法可能没有用 @Validated 注释 - 例如setName 在我上面的示例中 - 无论如何都会触发建议。您必须通过反射来搜索注释,并且只有在找到时才执行验证。

我还扩展了方面以涵盖构造函数的验证注释并调整了主要 class 以测试:

package de.scrum_master.app;

public class WovenClass {
  private int value;
  private String name;

  public WovenClass() {
  }

  @Validated({ Group1.class, Group2.class })
  public WovenClass(int value, String name) {
    this.value = value;
    this.name = name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Validated({ Group1.class, Group2.class })
  public void setField(int value) {
    this.value = value;
  }

  public static void main(String[] args) {
    // Should not be intercepted (no validation annotation)
    new WovenClass().setName("foo");
    // Should yield positive validation
    new WovenClass().setField(11);
    // Should yield positive validation
    new WovenClass(22, "bar");
    // Should yield negative validation -> exception
    try {
      new WovenClass(0, "zot");
    } catch (RuntimeException e) {
      System.out.println("  " + e);
    }
    // Should yield negative validation -> exception
    try {
      new WovenClass().setField(0);
    } catch (RuntimeException e) {
      System.out.println("  " + e);
    }
  }
}
package de.scrum_master.aspect;

import java.lang.reflect.Executable;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.EnclosingStaticPart;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;

import de.scrum_master.app.Validated;

@Aspect
public class ValidationAspect {
  @Before("set(* *) && args(value)")
  public void validateFieldSet(JoinPoint jp, EnclosingStaticPart enclosing, Object value) {
    System.out.println(jp);
    Signature signature = enclosing.getSignature();
    boolean isMethod = signature instanceof MethodSignature;
    System.out.println("  Enclosing " + (isMethod ? "method: " : "constructor: ") + enclosing);

    Executable executable = isMethod
      ? ((MethodSignature) signature).getMethod()
      : ((ConstructorSignature) signature).getConstructor();
    Validated[] validatedAnnotations = executable.getDeclaredAnnotationsByType(Validated.class);
    if (validatedAnnotations.length == 0)
      System.out.println("  @Validated not found, skipping validation");

    for (Validated validated : validatedAnnotations) {
      System.out.println("  Validation rule:  " + validated);
      System.out.println("  Value to be set:  " + value);
      // Some random condition in order to mimic a negative validation
      if (value.equals(0))
        throw new RuntimeException("field validation failed");
    }
  }
}

控制台日志将更改为:

set(String de.scrum_master.app.WovenClass.name)
  Enclosing method: execution(void de.scrum_master.app.WovenClass.setName(String))
  @Validated not found, skipping validation
set(int de.scrum_master.app.WovenClass.value)
  Enclosing method: execution(void de.scrum_master.app.WovenClass.setField(int))
  Validation rule:  @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  Value to be set:  11
set(int de.scrum_master.app.WovenClass.value)
  Enclosing constructor: execution(de.scrum_master.app.WovenClass(int, String))
  Validation rule:  @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  Value to be set:  22
set(String de.scrum_master.app.WovenClass.name)
  Enclosing constructor: execution(de.scrum_master.app.WovenClass(int, String))
  Validation rule:  @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  Value to be set:  bar
set(int de.scrum_master.app.WovenClass.value)
  Enclosing constructor: execution(de.scrum_master.app.WovenClass(int, String))
  Validation rule:  @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  Value to be set:  0
  java.lang.RuntimeException: field validation failed
set(int de.scrum_master.app.WovenClass.value)
  Enclosing method: execution(void de.scrum_master.app.WovenClass.setField(int))
  Validation rule:  @de.scrum_master.app.Validated(value={de.scrum_master.app.Group1.class, de.scrum_master.app.Group2.class})
  Value to be set:  0
  java.lang.RuntimeException: field validation failed

thisEnclosingJoinPointStaticPartEnclosingStaticPart

的资源

How could I know existence of EnclosingStaticPart arg? I think I searched into AspectJ references kind of deeply though. It would be nice if you could provide me a reference or something about it.