尝试通过反射访问时参数注释为 null 'getAnnotatedParameterTypes()'
Parameter annotation is null when trying to access it via reflection 'getAnnotatedParameterTypes()'
我正在使用 AspectJ 编织一个方法并应用 Around
建议。然后在建议逻辑中,我想访问该方法的所有带注释的参数。我这样做是为了过滤我正在寻找的特定注释。
问题是,在我调用 java.lang.reflect
的 getAnnotatedParameterTypes()
之后,我收到一个 AnnotatedType
的数组。我可以在那里找到我正在寻找的预期参数。但是,当我想访问该参数的注释类型时 - 因为我想按其类型进行过滤 - 不存在注释。
我预计它会出现 - 既然它说它是一个 AnnotatedType
- 那么注释在哪里 :D
下面是要查看的代码
@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()));
}
}
日志输出
2020-10-10 22:17:11.821 INFO 215068 --- [ Test worker] com.mystuff.Aspect : []
我的批注
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query{
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Response {
}
测试整个魔法的class
@Component
class TestCandidate {
@Query
public TestResponseModel useData(@Response TestResponseModel model){
return model;
}
}
您的方面代码有几个问题:
你的目标方法 return 是某种东西,但建议方法的 return 类型是 void
,即它隐式地永远不会匹配 [=17= 以外的任何东西] 方法。不过,它肯定不符合您的示例 useData(..)
方法。所以如果你想限制return类型,你需要让return类型Object
或TestResponseModel
。
@Around
建议永远不会调用 joinPoint.proceed()
,即目标方法不会被执行,而是被跳过。
如果您只想记录 @Response
参数而不修改任何参数或结果 before/after 继续,实际上一个简单的 @Before
建议就足够了。不过,我会在我的示例代码中保留你的建议,以防万一你想用这些参数做一些特别的事情。
建议方法的前两行执行以下操作:
- 获取目标中所有方法的数组class。
- 找到第一个方法。
这没有多大意义。为什么你总是用第一种方法做某事而不考虑它是什么方法?你想识别被建议拦截的目标方法的参数注释,不是吗?可能第一个方法的第一个参数没有任何注释,这就是 none 被记录的原因。您实际上很幸运,第一个方法根本没有参数,否则 annotatedParameterTypes[0]
会产生“数组索引越界”异常。
这是您想改为执行的操作。顺便说一句,我在这里展示了一个完整的 MCVE,正如你一开始就应该做的那样。我使用的是普通的 AspectJ,而不是 Spring AOP,所以我不使用任何 @Component
注释。但是,如果您是 Spring 用户,则只需将方面和目标都设置为 class Spring components/beans 即可使其正常工作。
注释 + 虚拟助手 class:
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 {}
目标 class 与 positive/negative 测试用例 + 驱动程序应用程序
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);
}
}
预期会触发方法 useData
和 multipleResponses
的建议,并且后一种方法中多个 @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(* *(.., @de.scrum_master.app.Response (*), ..))"
)
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
return 类型,不需要调用 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
我正在使用 AspectJ 编织一个方法并应用 Around
建议。然后在建议逻辑中,我想访问该方法的所有带注释的参数。我这样做是为了过滤我正在寻找的特定注释。
问题是,在我调用 java.lang.reflect
的 getAnnotatedParameterTypes()
之后,我收到一个 AnnotatedType
的数组。我可以在那里找到我正在寻找的预期参数。但是,当我想访问该参数的注释类型时 - 因为我想按其类型进行过滤 - 不存在注释。
我预计它会出现 - 既然它说它是一个 AnnotatedType
- 那么注释在哪里 :D
下面是要查看的代码
@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()));
}
}
日志输出
2020-10-10 22:17:11.821 INFO 215068 --- [ Test worker] com.mystuff.Aspect : []
我的批注
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query{
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Response {
}
测试整个魔法的class
@Component
class TestCandidate {
@Query
public TestResponseModel useData(@Response TestResponseModel model){
return model;
}
}
您的方面代码有几个问题:
你的目标方法 return 是某种东西,但建议方法的 return 类型是
void
,即它隐式地永远不会匹配 [=17= 以外的任何东西] 方法。不过,它肯定不符合您的示例useData(..)
方法。所以如果你想限制return类型,你需要让return类型Object
或TestResponseModel
。@Around
建议永远不会调用joinPoint.proceed()
,即目标方法不会被执行,而是被跳过。如果您只想记录
@Response
参数而不修改任何参数或结果 before/after 继续,实际上一个简单的@Before
建议就足够了。不过,我会在我的示例代码中保留你的建议,以防万一你想用这些参数做一些特别的事情。建议方法的前两行执行以下操作:
- 获取目标中所有方法的数组class。
- 找到第一个方法。
这没有多大意义。为什么你总是用第一种方法做某事而不考虑它是什么方法?你想识别被建议拦截的目标方法的参数注释,不是吗?可能第一个方法的第一个参数没有任何注释,这就是 none 被记录的原因。您实际上很幸运,第一个方法根本没有参数,否则
annotatedParameterTypes[0]
会产生“数组索引越界”异常。
这是您想改为执行的操作。顺便说一句,我在这里展示了一个完整的 MCVE,正如你一开始就应该做的那样。我使用的是普通的 AspectJ,而不是 Spring AOP,所以我不使用任何 @Component
注释。但是,如果您是 Spring 用户,则只需将方面和目标都设置为 class Spring components/beans 即可使其正常工作。
注释 + 虚拟助手 class:
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 {}
目标 class 与 positive/negative 测试用例 + 驱动程序应用程序
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);
}
}
预期会触发方法 useData
和 multipleResponses
的建议,并且后一种方法中多个 @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(* *(.., @de.scrum_master.app.Response (*), ..))"
)
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
return 类型,不需要调用 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