当前位置: 首页 > 知识库问答 >
问题:

尝试通过反射访问内部类构造函数的参数注释时出现ArrayIndexOutOfBoundsException

邹毅
2023-03-14

我正在尝试使用简单的自定义@NotNull注释对我的方法执行空检查,即我将方法声明为mymethod(@NotNull String name, String description),当有人调用此方法并将空值作为'name'参数传递时,会引发异常。

我已经使用aspectj实现了一个简单方面。这个解决方案对我来说很有效。一个例外是内部类的构造函数。在这种情况下,由于java内部的异常,方面崩溃。郎。反思。参数:

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 { }

测试等级:

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将封闭的类对象作为第一个参数传递给内部类的构造函数引起的(我被告知这是标准行为)。问题是,params[i]。executable.getParameterAnnoations()似乎不知道附加参数,只返回“正常”参数的注释

我觉得这是aspectj或java中的一个bug。郎。反思。但由于我找不到任何关于这个的bug报告,我更可能是做错了什么。该应用程序运行在java 8(尝试了oracle jdk的多个不同版本和上一个openjkd版本)和aspectj 1.8.13(但也尝试了1.9.4)上。

所以我的问题是:这是一个已知的错误吗?我的实现中有什么缺陷吗?有一些解决方法吗?(我想手动匹配注释和参数并不难。但是由于我对java反射的了解非常有限,我真的无法预见后果)。

编辑:提供工作示例

共有1个答案

许焕
2023-03-14

好吧,我太好奇了,玩弄了我自己的MCVE。我可以排除AeyJ是罪魁祸首,并将问题归咎于JDK/JRE问题:

内部(非静态)类构造函数的问题是,它们的第一个参数始终是外部对象的实例。Java 8-我在1.8.0_152和1.8.0_211中都试过-包含一个bug的反射。基本上,它将实际内部构造函数参数的注释上移一个索引,例如,第一个构造函数参数的注释参数存储在索引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后,您应该没事。

 类似资料:
  • 我用AspectJ编写了一个方法,并对其应用了一个建议。在advice逻辑中,我希望访问该方法的所有注释参数。我这样做是为了过滤我正在寻找的特定注释。 问题是,在我调用后,我收到了一个数组。我可以在那里找到我正在寻找的预期参数。但是,当我想访问该参数的注释类型时——因为我想按其类型过滤——没有注释存在。 我希望它会出现-因为它说它是一个带注释的类型-那么注释在哪里:D 这是要查看的代码 日志输出

  • 我在MyController类下有一个函数: 我在我的项目中设置了AspectJ,以便在这两个addPerson(…)时运行AOP逻辑上述方法称为: 我的问题在上述代码注释中提到。我想知道如何访问AOP函数中用RequestBody注释的参数?我不想检查参数类型或名称,但想知道如何通过检查注释来访问参数。有可能吗?

  • 问题内容: 这是一个测试类: 这是我的输出: 我缺少通过反射使注释可见的什么? 我是否仅需要检查它们的存在就需要注释处理器? 问题答案: 为了在运行时访问注释,它需要具有运行时的保留策略。 否则,注释将被丢弃,并且JVM无法识别它们。 有关更多信息,请参见此处。

  • 为了能够 动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 类型的对象或者数组。 getConstructors() getConstructor(Class<?>…parameterTypes) getDeclaredConstructors() getDeclaredConstructor(Class<?>...parameterTypes) 如果是访问指定的构造方法,需要根据该构

  • 根据JSR308(Java类型注释),可以使用elementtype.type_use注释任何类型:

  • 我想在构造函数中访问多个位置的状态值。当我在构造函数中输入console.log(this)时,我可以看到可以访问特定的值,但是当我输入console.log this.state时,我没有定义。 这就是我想象的工作方式(我想访问CSSValue中的状态): 但是,当我运行此代码: 然后我在控制台中得到这个: 但是当我将console.logconsole.log(this.state)它打印未定