如何直接在方法和另一个注释(然后在方法上使用)上定义注释的切入点? [英] How to define the Pointcut(s) for annotation directly on method and on another annotation (which is then used on a method)?

查看:68
本文介绍了如何直接在方法和另一个注释(然后在方法上使用)上定义注释的切入点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想弄清楚如何定义切入点以及如何使用 spring AOP 处理多个注释.

I am trying to figure out how to define Pointcuts and how to handle multiple annotations using spring AOP.

我有以下自定义注释:

@RequiresNonBlank

@Retention (RetentionPolicy.RUNTIME)
@Target (value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented
public @interface RequiresNonBlank {
  String value();
  Class<? extends StuffException> throwIfInvalid();
}

@RequiresNonBlankDummy

@Retention (RetentionPolicy.RUNTIME)
@Target (value = {ElementType.METHOD})
@Documented
@RequiresNonBlank (
    value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).value",
    throwIfInvalid = TestDummyStuffException.class
)
@interface RequiresNonBlankDummy {
}

我有以下虚拟控制器:

TestDummyController

@Component
public class TestDummyController {
  @RequiresNonBlank (
      value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).value",
      throwIfInvalid = TestDummyStuffException.class
  )
  public boolean methodWithRequiresNonBlankAnnotation() {
    return true;
  }

  @RequiresNonBlankDummy
  public boolean methodWithRequiresNonBlankDummyAnnotation() {
    return true;
  }

  @RequiresNonBlankDummy
  @RequiresNonBlank (
      value = "T(com.stuff.exceptions.annotation.TestDummyValueHolder).anotherValue",
      throwIfInvalid = TestDummyStuffException.class
  )
  public boolean methodWithMultipleRequiresNonBlankAnnotation() {
    return true;
  }
}

我的 TestDummyValueHolder 是一个类,它只包含两个字符串(valueanotherValue)及其对应的 getter 和 setter.

My TestDummyValueHolder is a class, which only contains two Strings (value and anotherValue) and their corresponding getters and setters.

我想定义一个或多个切入点 (@Pointcut),它将处理一个或多个/堆叠的 @RequiresNonBlank 方法(派生"注释,例如因为 @RequiresNonBlankDummy 应该被考虑在内).

I want to define one or multiple pointcuts (@Pointcut), which would handle one or multiple / stacked @RequiresNonBlank on a method ("derived" annotations such as @RequiresNonBlankDummy should be taken into account).

我的切面处理程序目前看起来像这样:

My aspect handler currently looks like this:

RequiresNonBlankAspect

@Component
@Aspect
@Slf4j
public class RequiresNonBlankAspect {
  private static final String REQUIRES_NON_BLANK_FQPN =
      "com.stuff.exceptions.annotation.RequiresNonBlank";
  @Before("execution(@" + REQUIRES_NON_BLANK_FQPN + " * *(..)) && @annotation(annotation)")
  public void evaluatePreconditionItself(JoinPoint joinPoint, RequiresNonBlank annotation) {
    evaluatePrecondition(joinPoint, annotation);
  }

  @Before("execution(@(@" + REQUIRES_NON_BLANK_FQPN + " *) * *(..)) && @annotation(annotation)")
  public void evaluatePreconditionOnAnnotation(JoinPoint joinPoint, RequiresNonBlank annotation) {
    evaluatePrecondition(joinPoint, annotation);
  }

  private void evaluatePrecondition(JoinPoint joinPoint, RequiresNonBlank annotation) {
    try {
      Objects.requireNonNull(annotation);
    } catch (NullPointerException e) {
      log.error("No annotation found!", e);
    }

    ExpressionParser elParser = new SpelExpressionParser();
    Expression expression = elParser.parseExpression(annotation.value());

    String expressionToEvaluate = (String) expression.getValue(joinPoint.getArgs());
    log.info("value to check: {}", expressionToEvaluate);

    if (StringUtils.isEmpty(expressionToEvaluate)) {
      try {
        throw annotation.throwIfInvalid().getConstructor().newInstance();
      } catch (InstantiationException | IllegalAccessException | InvocationTargetException
          | NoSuchMethodException e) {
        log.error("Could not throw the exception configured!", e);
      }
    }
  }
}

但是:annotationOnAnnotation(...) 不起作用.annotationItself 但是确实如此.

However: annotationOnAnnotation(...) does not work. annotationItself however does.

我有以下测试:

RequiresNonBlankTest

@SpringBootApplication
@ActiveProfiles (profiles = "test")
@RunWith (SpringRunner.class)
public class RequiresNonBlankTest {

  @Autowired
  private TestDummyController controller;

  @Test (expected = TestDummyStuffException.class)
  public void testRequiresNonBlank_valueIsNull() {
    TestDummyValueHolder.setValue(null);
    controller.methodWithRequiresNonBlankAnnotation();
  }

  @Test
  public void testRequiresNonBlank_valueIsNotNull() {
    TestDummyValueHolder.setValue("value: non-null");
    assertThat(controller.methodWithRequiresNonBlankAnnotation(), equalTo(true));
  }

  @Test (expected = TestDummyStuffException.class)
  public void testRequiresNonBlankDummy_valueIsNull() {
    TestDummyValueHolder.setValue(null);
    controller.methodWithRequiresNonBlankDummyAnnotation();
  }

  @Test
  public void testRequiresNonBlankDummy_valueIsNotNull() {
    TestDummyValueHolder.setValue("value: non-null");
    assertThat(controller.methodWithRequiresNonBlankDummyAnnotation(), equalTo(true));
  }
}

然而:测试 testRequiresNonBlankDummy_valueIsNull() 失败.

我还想知道如何不仅对方法上的注释做出反应(参见 @RequiresNonBlankDummy),而且还想知道我在 TestDummyController#methodWithMultipleRequiresNonBlankAnnotation(堆叠/多个注释).这可能吗?如果可能,怎么办?

I also would like to know how to not only react to annotations on annotations on a method (see @RequiresNonBlankDummy), but also to what I have in TestDummyController#methodWithMultipleRequiresNonBlankAnnotation (stacked / multiple annotations). Is this possible and if so, how?

我正在使用 Spring Boot 和 Spring AOP.我曾尝试使用 AnnotationUtilsAnnotationElementUtils,但至少从我所知,它没有帮助.请帮助我或给我一个如何解决这个问题的提示.

I am using Spring Boot and Spring AOP. I have tried using AnnotationUtils and AnnotationElementUtils, but I at least from what I can tell it did not help. Please help me or give me a hint on how to solve this.

编辑 (15.08.2021):

Edit (15.08.2021):

TestDummyValueHolder

public class TestDummyValueHolder {
  private static String value;
  private static String anotherValue;

  public static String getValue() {
    return TestDummyValueHolder.value;
  }

  public static void setValue(String value) {
    TestDummyValueHolder.value = value;
  }

  public static String getAnotherValue() {
    return TestDummyValueHolder.anotherValue;
  }

  public static void setAnotherValue(String anotherValue) {
    TestDummyValueHolder.anotherValue = anotherValue;
  }
}

StuffException 非常通用.事实上,您可以用任何具有无参数构造函数的异常替换它.我还更新了方面处理程序(RequiresNonBlankAspect),就像@kriegaex 提到的那样.

StuffException is pretty generic. In fact you could replace it with any Exception, which has a no-args constructor. I have also updated the aspect handler (RequiresNonBlankAspect) to be like what @kriegaex mentioned.

推荐答案

在 OP 更新他的问题后写了一个新答案,纠正了开始时的错误.现在旧的答案不再适合.

好的,剩下的问题是:

  • 现在我们从使用 || 链接两个切入点更改为具有两个单独的切入点,注释绑定的工作更可靠.不过请注意:

  • Now that we changed from chaining two pointcuts with || to having two separate pointcuts, the annotation binding works more reliably. Please note, though:

  • 对于同时带有普通注解和元注解的方法 methodWithMultipleRequiresNonBlankAnnotation,两个通知都会被触发,也就是说,你可能只希望发生一次,但现在实际上发生了两次.
  • OTOH,您实际上可能想要触发两个建议,因为有两个不同的 SpEL 表达式 T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue(在 @RequiresNonBlankcode>) 和T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value(在@RequiresNonBlankDummy的注解中),那么这个其实更好.这取决于您的要求.
  • For method methodWithMultipleRequiresNonBlankAnnotation which carries both the normal and the meta annotation, both advices are triggered, i.e. something that maybe you only expect to happen once, now actually happens twice.
  • OTOH, you might actually want two advices firing, because there are two different SpEL expressions T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue (in @RequiresNonBlank) and T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value (in the annotation of @RequiresNonBlankDummy), then this is actually better. It depends on your requirement.

您将 @annotation(annotation) 与参数绑定 RequiresNonBlank annotation 结合使用会阻止通知为 @RequiresNonBlankDummy 触发> (method methodWithRequiresNonBlankDummyAnnotation),因为两种注解类型不兼容,没有一种注解类型扩展另一个或实现接口这样的东西.因此,在这种情况下,您只需要使用没有参数绑定的 poinctut,并通过通知方法内部的反射找到注释.

Your usage of @annotation(annotation) in combination with the parameter binding RequiresNonBlank annotation stops the advice from firing for @RequiresNonBlankDummy (method methodWithRequiresNonBlankDummyAnnotation), because the two annotation types are incompatible and there is no such thing as one annotation type extending another or implementing interfaces. So all you are left with in this case is to use the poinctuts without parameter binding and find the annotations via reflection from inside the advice methods.

更新: 好吧,我假设在直接和元注释的情况下,您都希望同时触发两个建议.解决方案如下所示:

Update: OK, I made the assumption that in case of both a direct and a meta annotation you want both advices firing. The solution then looks like this:

package de.scrum_master.spring.q68785567;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

@Component
@Aspect
//@Slf4j
public class RequiresNonBlankAspect {
  private static final Logger log = LoggerFactory.getLogger(RequiresNonBlankAspect.class.getName());
  private static final String REQUIRES_NON_BLANK_FQPN =
    "de.scrum_master.spring.q68785567.RequiresNonBlank";
  private ExpressionParser elParser = new SpelExpressionParser();

  @Before("execution(@" + REQUIRES_NON_BLANK_FQPN + " * *(..)) && @annotation(requiresNonBlank)")
  public void evaluatePreconditionItself(JoinPoint joinPoint, RequiresNonBlank requiresNonBlank) {
    log.info("[DIRECT] " + joinPoint + " -> " + requiresNonBlank);
    evaluatePrecondition(joinPoint, requiresNonBlank);
  }

  @Before("execution(@(@" + REQUIRES_NON_BLANK_FQPN + " *) * *(..))")
  public void evaluatePreconditionOnAnnotation(JoinPoint joinPoint) {
    RequiresNonBlank requiresNonBlank = getRequiresNonBlankMeta(joinPoint);
    log.info("[META]   " + joinPoint + " -> " + requiresNonBlank);
    evaluatePrecondition(joinPoint, requiresNonBlank);
  }

  private RequiresNonBlank getRequiresNonBlankMeta(JoinPoint joinPoint) {
    RequiresNonBlank requiresNonBlank = null;
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    Annotation[] annotations = method.getAnnotations();
    for (Annotation annotation : annotations) {
      RequiresNonBlank requiresNonBlankMeta = annotation.annotationType().getAnnotation(RequiresNonBlank.class);
      if (requiresNonBlankMeta != null) {
        requiresNonBlank = requiresNonBlankMeta;
        break;
      }
    }
    return requiresNonBlank;
  }

  public void evaluatePrecondition(JoinPoint joinPoint, RequiresNonBlank requiresNonBlank) {
    try {
      Objects.requireNonNull(requiresNonBlank);
    }
    catch (NullPointerException e) {
      log.error("No annotation found!", e);
    }

    Expression expression = elParser.parseExpression(requiresNonBlank.value());
    String expressionToEvaluate = (String) expression.getValue(joinPoint.getArgs());
    log.info("Evaluated expression: " + expressionToEvaluate);
    if (StringUtils.isEmpty(expressionToEvaluate)) {
      try {
        throw requiresNonBlank.throwIfInvalid().getConstructor().newInstance();
      }
      catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        log.error("Could not throw the exception configured!", e);
      }
    }
  }
}


更新 2:这个要点是我对对可重复注解、多个元注解和混合它们的新要求,但使用板载 Java 手段而不是 Spring 注解实用程序,以便也使其在 Spring 上下文之外的本机 AspectJ 应用程序中工作.顺便说一句,我重命名了一些类和方法,因为对我来说,你的名字太相似了,而且有点缺乏表达.


Update 2: This gist is my take on the new requirement of repeatable annotations, multiple meta annotations and mixing them, but using on-board Java means instead of Spring annotation utilities in order to also make it work in native AspectJ applications outside of a Spring context. BTW, I renamed some classes and methods because for me your names were too similar and somewhat inexpressive.

这篇关于如何直接在方法和另一个注释(然后在方法上使用)上定义注释的切入点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆