ArrayIndexOutOfBoundsException при попытке доступа к аннотациям параметров для внутреннего конструктора класса с помощью отражения - PullRequest
0 голосов
/ 27 мая 2019

Я пытаюсь выполнить нулевые проверки моих методов, используя простую пользовательскую аннотацию @NotNull, т.е. я объявляю метод как myMethod(@NotNull String name, String description), и когда кто-то вызывает этот метод с нулевым значением, переданным в качестве аргумента 'name', возникает исключение.

У меня уже есть реализация простого аспекта с использованием aspectj.Это решение работает довольно хорошо для меня.Единственное исключение - конструкторы внутренних классов.В этом случае аспект падает из-за исключения внутри 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 { }

Тестовый класс:

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.getParameterAnnotations(), похоже, не знает о дополнительном аргументе и возвращает аннотации только для "нормальных" параметров

Мне кажется, что это ошибка в aspectj или java.lang.reflection.Но так как я не могу найти сообщение об ошибке, мне кажется, что я делаю что-то не так.Приложение работает на Java 8 (пробовал несколько разных сборок oracle jdk и последней сборки openjkd) и aspectj 1.8.13 (но пробовал также 1.9.4).

Так что мой вопрос (а): этоизвестная ошибка?Есть ли какой-то недостаток в моей реализации?Есть ли обходной путь?(Полагаю, было бы не сложно сопоставить аннотации с параметрами вручную. Но поскольку у меня очень ограниченные знания об java-отражении, я не могу предвидеть последствия).

Отредактировано: при условии работыпример

1 Ответ

2 голосов
/ 28 мая 2019

Хорошо, мне было слишком любопытно, и я играл с моим собственным MCVE.Я мог исключить AspectJ как виновника и связать проблему с проблемой JDK / JRE:

С конструкторами внутренних (не статичных) классов дело в том, что их первый параметр всегда является экземпляром внешнего объекта,Java 8 - я пробовал как с 1.8.0_152, так и с 1.8.0_211 - содержит ошибочное отражение.По сути, он перемещает аннотации реальных параметров внутреннего конструктора на один индекс вверх, например, параметры аннотации для первого аргумента конструктора хранятся в индексе 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 с вами все будет в порядке.

...