尝试通过反射'getAnnotatedParameterTypes'访问时,参数注释为null

问题描述

我正在用AspectJ编织一种方法,并对其应用了Around建议。然后,在建议逻辑中,我想访问该方法的所有带注释的参数。我这样做是为了过滤我要查找的特定注释。

问题在于,在我调用getAnnotatedParameterTypes()的{​​{1}}之后,我收到了java.lang.reflect的数组。我可以在那里找到期望的参数。但是,当我要访问该参数的注释类型时-因为我想按其类型进行过滤-没有注释。

我希望它会出现-很好,因为它说它是AnnotatedType-那么注释:D

在哪里

这是要浏览的代码

AnnotatedType

日志输出

2020-10-10 22:17:11.821信息215068 --- [测试人员] com.mystuff.Aspect:[]

我的注释

    @Around("@annotation(com.mystuff.client.annotation.Query)")
    public void doStuff(ProceedingJoinPoint joinPoint) {
        Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();
        Optional<Method> first = Arrays.stream(methods).findFirst();
        if (first.isPresent()) {
            Method method = first.get();
            AnnotatedType[] annotatedParameterTypes = method.getAnnotatedParameterTypes();
            AnnotatedType annotatedParameterType = annotatedParameterTypes[0];
            LOG.info(Arrays.toString(annotatedParameterType.getAnnotations()));
        }
    }

测试整个魔术的课程

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query{

}


@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Response {

}

解决方法

您的方面代码有几个问题:

  • 您的目标方法返回了一些内容,但是advice方法的返回类型为void,也就是说,它隐式绝不会匹配void方法以外的任何内容。但是,它绝对不会与您的示例useData(..)方法匹配。因此,如果要限制返回类型,则需要将返回类型设为ObjectTestResponseModel

  • @Around建议永远不会调用joinPoint.proceed(),即目标方法将不会执行而是被跳过。

  • 如果您只想记录@Response参数,而又不想在继续之前/之后修改任何参数或结果,那么实际上一个简单的@Before建议就足够了。不过,如果您想对这些参数做一些特殊的事情,我将在示例代码中保留您的周围建议。

  • 建议方法的前两行执行以下操作:

    1. 获取目标类中所有方法的数组。
    2. 找到第一种方法。

    这没有多大意义。为什么您总是不考虑第一种方法而总是用第一种方法做某事?您想在建议所拦截的目标方法上标识参数注释,不是吗?第一个方法的第一个参数可能没有任何注释,这就是为什么没有任何注释的原因。您实际上很幸运,第一个方法根本没有参数,否则annotatedParameterTypes[0]会产生“数组索引超出范围”异常。

这是您要执行的操作。顺便说一句,我在这里提供完整的MCVE,就像您应该首先做的那样。我使用的是普通AspectJ,而不是Spring AOP,所以我不使用任何@Component注释。但是,如果您是Spring用户,则可以同时使方面和目标类Spring组件/ bean都可以正常工作。

注释+虚拟助手类:

package de.scrum_master.app;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface Query {}
package de.scrum_master.app;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Response {}
package de.scrum_master.app;

public class TestResponseModel {}

带有正/负测试用例和驱动程序应用程序的目标类

package de.scrum_master.app;

class TestCandidate {
  @Query
  public TestResponseModel useData(@Response TestResponseModel model) {
    return model;
  }

  @Query
  public TestResponseModel dummyOne(TestResponseModel model) {
    return model;
  }

  public TestResponseModel dummyTwo(@Response TestResponseModel model) {
    return model;
  }

  @Query
  public TestResponseModel multipleResponses(@Response TestResponseModel model,@Response String anotherResponse,int i) {
    return model;
  }
  public static void main(String[] args) {
    TestCandidate candidate = new TestCandidate();
    TestResponseModel model = new TestResponseModel();
    candidate.dummyOne(model);
    candidate.dummyTwo(model);
    candidate.useData(model);
    candidate.multipleResponses(model,"foo",11);
  }
}

期望将为方法useDatamultipleResponses触发建议,并且后一种方法中多个@Response参数的特殊情况也可以由方面正确处理。

@Around方面的变体:

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import de.scrum_master.app.Response;

@Aspect
public class QueryResponseInterceptor {
  @Around(
    "@annotation(de.scrum_master.app.Query) && " +
    "execution(* *(..,@de.scrum_master.app.Response (*),..))"
  ) 
  public Object doStuff(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println(joinPoint);
    Object[] args = joinPoint.getArgs();
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
    for (int i = 0; i < args.length; i++) {
      for (Annotation annotation : annotationMatrix[i]) {
        if (annotation.annotationType().equals(Response.class)) {
          System.out.println("  " + args[i]);
          break;
        }
      }
    } 
    return joinPoint.proceed();
  }
}

请注意,execution()切入点如何限制带有参数的方法带有@Response注释的参数,无论它们在参数列表中的何处出现。

@Before方面的变体:

如果您只想记录带注释的参数,则可以使用以下更简单的变体:@Before建议和更少的样板:

package de.scrum_master.aspect;

import java.lang.annotation.Annotation;

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

import de.scrum_master.app.Response;

@Aspect
public class QueryResponseInterceptor {
  @Before(
    "@annotation(de.scrum_master.app.Query) && " +
    "execution(* *(..,..))"
  ) 
  public void doStuff(JoinPoint joinPoint) {
    System.out.println(joinPoint);
    Object[] args = joinPoint.getArgs();
    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
    for (int i = 0; i < args.length; i++) {
      for (Annotation annotation : annotationMatrix[i]) {
        if (annotation.annotationType().equals(Response.class)) {
          System.out.println("  " + args[i]);
          break;
        }
      }
    } 
  }
}

看到了吗?现在,您确实可以使用void返回类型,无需调用proceed(),因此也不必抛出Throwable

控制台日志:

对于两个方面的变体,控制台日志都是相同的。

execution(TestResponseModel de.scrum_master.app.TestCandidate.useData(TestResponseModel))
  de.scrum_master.app.TestResponseModel@71318ec4
execution(TestResponseModel de.scrum_master.app.TestCandidate.multipleResponses(TestResponseModel,String,int))
  de.scrum_master.app.TestResponseModel@71318ec4
  foo

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...