尝试通过反射访问内部 class 构造函数的参数注释时出现 ArrayIndexOutOfBoundsException
ArrayIndexOutOfBoundsException when trying to access parameter annotations for inner class constructor via reflection
我正在尝试使用简单的自定义 @NotNull 注释对我的方法执行 null 检查,即
我将方法声明为 myMethod(@NotNull String name, String description)
,当有人使用作为 'name' 参数传递的空值调用此方法时,将抛出异常。
我已经使用 aspectj 实现了一个简单的方面。这个解决方案对我来说效果很好。一个例外是内部 classes 的构造函数。在这种情况下,由于 java.lang.reflect.Parameter:
内部的异常,方面崩溃
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at java.lang.reflect.Parameter.getDeclaredAnnotations(Parameter.java:305)
at java.lang.reflect.Parameter.declaredAnnotations(Parameter.java:342)
at java.lang.reflect.Parameter.getAnnotation(Parameter.java:287)
at java.lang.reflect.Parameter.getDeclaredAnnotation(Parameter.java:315)
at ValidationAspect.checkNotNullArguments(ValidationAspect.java:22)
at OuterClass$InnerClass.<init>(OuterClass.java:4)
at OuterClass.constructInnerClass(OuterClass.java:14)
at Main.main(Main.java:5)
简化实施:
看点:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.ConstructorSignature;
import java.lang.reflect.Parameter;
@Aspect
public class ValidationAspect {
@Pointcut("execution(*.new(.., @NotNull (*), ..))")
private void anyConstructorWithNotNullParam() {}
@Before("anyConstructorWithNotNullParam()")
public void checkNotNullArguments(JoinPoint joinPoint) {
ConstructorSignature signature = (ConstructorSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
Parameter[] params = signature.getConstructor().getParameters();
for(int i = 0; i < args.length; i++) {
if(params[i].getDeclaredAnnotation(NotNull.class) != null) {
if (args[i] == null) {
throw new IllegalArgumentException("Illegal null argument");
}
}
}
}
}
注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER})
public @interface NotNull { }
测试class:
public class OuterClass {
public class InnerClass {
public InnerClass(
@NotNull String name
) {
System.out.println(String.format("Construct inner class with name: %s", name));
}
}
public InnerClass constructInnerClass(
String name
) {
return new InnerClass(name);
}
}
用法:
public class Main {
public static void main(String[] args) {
OuterClass outObj = new OuterClass();
outObj.constructInnerClass("myName");
}
}
据我所知,这是由于 java 将封闭的 class 对象作为第一个参数传递给内部 class 的构造函数(我被告知是标准行为)。问题是,params[i].executable.getParameterAnnotations()
似乎不知道附加参数和 returns 注释仅适用于 "normal" 参数
我觉得这是 aspectj 或 java.lang.reflection 中的错误。但是由于我找不到任何关于此的错误报告,所以在我看来更有可能是我做错了什么。该应用程序在 java 8(尝试了多个不同的 oracle jdk 构建和最后一个 openjkd 构建)和 aspectj 1.8.13(但也尝试了 1.9.4)上运行。
所以我的问题是:这是一个已知错误吗?我的实施有什么缺陷吗?有什么解决方法吗? (我想手动将注释与参数匹配起来并不难。但是由于我对 java 反射的了解非常有限,所以我真的无法预见后果)。
已编辑:提供了工作示例
好吧,我太好奇了,把自己的MCVE玩了一把。我可以排除 AspectJ 作为罪魁祸首并将问题归结为 JDK/JRE 问题:
内部 (non-static) class 构造函数的问题是它们的第一个参数始终是外部对象的实例。 Java 8 - 我尝试使用 1.8.0_152 和 1.8.0_211 - 包含反射 off-by-one 错误。基本上它将真正的内部构造函数参数的注释向上移动一个索引,例如第一个构造函数参数的注释参数存储在索引 0 中,它实际上应该包含外部对象实例的注释。我猜我的示例代码解释得更好:
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 NotNull {}
package de.scrum_master.app;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
public class Application {
class Inner {
public Inner(@NotNull String text) {
System.out.println("Constructing inner with " + text);
}
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Constructor<Inner> constructor = Inner.class.getConstructor(Application.class, String.class);
System.out.println(constructor);
for (Parameter parameter : constructor.getParameters()) {
System.out.println(" " + parameter);
for (Annotation annotation : parameter.getAnnotations())
System.out.println(" " + annotation);
}
}
}
这重现了 JDK 8 的问题:
public de.scrum_master.app.Application$Inner(de.scrum_master.app.Application,java.lang.String)
de.scrum_master.app.Application arg0
@de.scrum_master.app.NotNull()
java.lang.String arg1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at java.lang.reflect.Parameter.getDeclaredAnnotations(Parameter.java:305)
at java.lang.reflect.Parameter.getAnnotations(Parameter.java:333)
at de.scrum_master.app.Application.main(Application.java:19)
但是如果你 运行 和 JDK 11(我用的是 11.0.2)一切都按预期工作,如果我使用像你这样的建议的方面:
public de.scrum_master.app.Application$Inner(de.scrum_master.app.Application,java.lang.String)
de.scrum_master.app.Application arg0
java.lang.String arg1
@de.scrum_master.app.NotNull()
我没有费心查看所有 JDK 发行说明,以查明这是有意还是偶然修复的,以及 JDK 版本(9、10、11 ),但至少我可以告诉你,更新到 JDK 11 后你应该没问题。
我正在尝试使用简单的自定义 @NotNull 注释对我的方法执行 null 检查,即
我将方法声明为 myMethod(@NotNull String name, String description)
,当有人使用作为 'name' 参数传递的空值调用此方法时,将抛出异常。
我已经使用 aspectj 实现了一个简单的方面。这个解决方案对我来说效果很好。一个例外是内部 classes 的构造函数。在这种情况下,由于 java.lang.reflect.Parameter:
内部的异常,方面崩溃Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at java.lang.reflect.Parameter.getDeclaredAnnotations(Parameter.java:305)
at java.lang.reflect.Parameter.declaredAnnotations(Parameter.java:342)
at java.lang.reflect.Parameter.getAnnotation(Parameter.java:287)
at java.lang.reflect.Parameter.getDeclaredAnnotation(Parameter.java:315)
at ValidationAspect.checkNotNullArguments(ValidationAspect.java:22)
at OuterClass$InnerClass.<init>(OuterClass.java:4)
at OuterClass.constructInnerClass(OuterClass.java:14)
at Main.main(Main.java:5)
简化实施:
看点:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.ConstructorSignature;
import java.lang.reflect.Parameter;
@Aspect
public class ValidationAspect {
@Pointcut("execution(*.new(.., @NotNull (*), ..))")
private void anyConstructorWithNotNullParam() {}
@Before("anyConstructorWithNotNullParam()")
public void checkNotNullArguments(JoinPoint joinPoint) {
ConstructorSignature signature = (ConstructorSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
Parameter[] params = signature.getConstructor().getParameters();
for(int i = 0; i < args.length; i++) {
if(params[i].getDeclaredAnnotation(NotNull.class) != null) {
if (args[i] == null) {
throw new IllegalArgumentException("Illegal null argument");
}
}
}
}
}
注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER})
public @interface NotNull { }
测试class:
public class OuterClass {
public class InnerClass {
public InnerClass(
@NotNull String name
) {
System.out.println(String.format("Construct inner class with name: %s", name));
}
}
public InnerClass constructInnerClass(
String name
) {
return new InnerClass(name);
}
}
用法:
public class Main {
public static void main(String[] args) {
OuterClass outObj = new OuterClass();
outObj.constructInnerClass("myName");
}
}
据我所知,这是由于 java 将封闭的 class 对象作为第一个参数传递给内部 class 的构造函数(我被告知是标准行为)。问题是,params[i].executable.getParameterAnnotations()
似乎不知道附加参数和 returns 注释仅适用于 "normal" 参数
我觉得这是 aspectj 或 java.lang.reflection 中的错误。但是由于我找不到任何关于此的错误报告,所以在我看来更有可能是我做错了什么。该应用程序在 java 8(尝试了多个不同的 oracle jdk 构建和最后一个 openjkd 构建)和 aspectj 1.8.13(但也尝试了 1.9.4)上运行。
所以我的问题是:这是一个已知错误吗?我的实施有什么缺陷吗?有什么解决方法吗? (我想手动将注释与参数匹配起来并不难。但是由于我对 java 反射的了解非常有限,所以我真的无法预见后果)。
已编辑:提供了工作示例
好吧,我太好奇了,把自己的MCVE玩了一把。我可以排除 AspectJ 作为罪魁祸首并将问题归结为 JDK/JRE 问题:
内部 (non-static) class 构造函数的问题是它们的第一个参数始终是外部对象的实例。 Java 8 - 我尝试使用 1.8.0_152 和 1.8.0_211 - 包含反射 off-by-one 错误。基本上它将真正的内部构造函数参数的注释向上移动一个索引,例如第一个构造函数参数的注释参数存储在索引 0 中,它实际上应该包含外部对象实例的注释。我猜我的示例代码解释得更好:
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 NotNull {}
package de.scrum_master.app;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
public class Application {
class Inner {
public Inner(@NotNull String text) {
System.out.println("Constructing inner with " + text);
}
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Constructor<Inner> constructor = Inner.class.getConstructor(Application.class, String.class);
System.out.println(constructor);
for (Parameter parameter : constructor.getParameters()) {
System.out.println(" " + parameter);
for (Annotation annotation : parameter.getAnnotations())
System.out.println(" " + annotation);
}
}
}
这重现了 JDK 8 的问题:
public de.scrum_master.app.Application$Inner(de.scrum_master.app.Application,java.lang.String)
de.scrum_master.app.Application arg0
@de.scrum_master.app.NotNull()
java.lang.String arg1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at java.lang.reflect.Parameter.getDeclaredAnnotations(Parameter.java:305)
at java.lang.reflect.Parameter.getAnnotations(Parameter.java:333)
at de.scrum_master.app.Application.main(Application.java:19)
但是如果你 运行 和 JDK 11(我用的是 11.0.2)一切都按预期工作,如果我使用像你这样的建议的方面:
public de.scrum_master.app.Application$Inner(de.scrum_master.app.Application,java.lang.String)
de.scrum_master.app.Application arg0
java.lang.String arg1
@de.scrum_master.app.NotNull()
我没有费心查看所有 JDK 发行说明,以查明这是有意还是偶然修复的,以及 JDK 版本(9、10、11 ),但至少我可以告诉你,更新到 JDK 11 后你应该没问题。