Почему AspectJ генерирует пустую проверку аннотаций?

Я использую плетение во время компиляции AspectJ 1.8.8, и у меня есть такой блок

@SomeAnnotation(value = "someValue")
public List doSomething(String someArg) {
    ...
}

где @SomeAnnotation реализован с советом «Вокруг».

Глядя на байт-код с JD-GUI, я вижу следующий сгенерированный код (слегка отформатированный):

public class SomeClass {
  private static Annotation ajc$anno$5;

  ...

  @SomeAnnotation(value = "someValue")
  public List doSomething(String someArg)
  {
    String str = someArg;
    JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_5, this, this, str);
    Object[] arrayOfObject = new Object[3];
    arrayOfObject[0] = this;
    arrayOfObject[1] = str;
    arrayOfObject[2] = localJoinPoint;
    Annotation tmp56_53 = ajc$anno$5;
    if (tmp56_53 == null) {
      tmp56_53;
    }
    return (List)new SomeClass.AjcClosure11(arrayOfObject).linkClosureAndJoinPoint(69648).around(tmp56_53, (SomeAnnotation)(ajc$anno$5 = SomeClass.class.getDeclaredMethod("doSomething", new Class[] { String.class }).getAnnotation(SomeAnnotation.class)));
  }
}

Мне было интересно, почему это условие (if (tmp56_53...)) вообще существует, поскольку оно, кажется, ничего не делает (и также является синтаксически неправильной Java? Может быть, потому, что это было сгенерировано ajc?). Мне любопытно, потому что это вызывает «промахи ветвей» в инструменте покрытия (JaCoCo).


Изменить 1

Вот необработанный машинный код Java из javap:

       0: aload_1
       1: astore_2
       2: getstatic     #480                // Field ajc$tjp_10:Lorg/aspectj/lang/JoinPoint$StaticPart;
       5: aload_0
       6: aload_0
       7: aload_2
       8: invokestatic  #312                // Method org/aspectj/runtime/reflect/Factory.makeJP:(Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;
      11: astore_3
      12: invokestatic  #339                // Method com/foo/SomeAspect.aspectOf:()Lcom/foo/SomeAspect;
      15: iconst_3
      16: anewarray     #2                  // class java/lang/Object
      19: astore        4
      21: aload         4
      23: iconst_0
      24: aload_0
      25: aastore
      26: aload         4
      28: iconst_1
      29: aload_2
      30: aastore
      31: aload         4
      33: iconst_2
      34: aload_3
      35: aastore
      36: new           #484                // class com/foo/SomeClass$AjcClosure21
      39: dup
      40: aload         4
      42: invokespecial #485                // Method com/foo/SomeClass$AjcClosure21."<init>":([Ljava/lang/Object;)V
      45: ldc_w         #327                // int 69648
      48: invokevirtual #333                // Method org/aspectj/runtime/internal/AroundClosure.linkClosureAndJoinPoint:(I)Lorg/aspectj/lang/ProceedingJoinPoint;
      51: getstatic     #488                // Field ajc$anno$10:Ljava/lang/annotation/Annotation;
      54: dup
      55: ifnonnull     86
      58: pop
      59: ldc           #75                 // class com/foo/SomeClass
      61: ldc_w         #489                // String someArg
      64: iconst_1
      65: anewarray     #348                // class java/lang/Class
      68: dup
      69: iconst_0
      70: ldc           #171                // class java/lang/String
      72: aastore
      73: invokevirtual #352                // Method java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
      76: ldc_w         #341                // class com/foo/SomeAnnotation
      79: invokevirtual #358                // Method java/lang/reflect/Method.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
      82: dup
      83: putstatic     #488                // Field ajc$anno$10:Ljava/lang/annotation/Annotation;
      86: nop
      87: checkcast     #341                // class com/foo/SomeAnnotation
      90: invokevirtual #362                // Method com/foo/SomeAspect.around:(Lorg/aspectj/lang/ProceedingJoinPoint;Lcom/foo/SomeAnnotation;)Ljava/lang/Object;
      93: pop
      94: return

Похоже, ifnonnull может быть условным, но я совсем не знаком с инструкциями JVM и до сих пор не знаю, почему AspectJ генерирует подобную логику.

Я подозреваю, что jd испытывает трудности с правильным анализом байт-кода, что может стать довольно странным, когда задействован AspectJ. Фактическая разборка метода может быть полезной. Мой WAG будет заключаться в том, что он предназначен для обеспечения запуска загрузки классов.

chrylis -cautiouslyoptimistic- 09.02.2019 01:05

@chrylis Спасибо за ответ. Я добавил дизассемблирование на случай, если это поможет, но я сам не совсем понимаю это.

Lycus 09.02.2019 02:42

Хорошо, это оказалось увлекательной кроличьей норой.

chrylis -cautiouslyoptimistic- 09.02.2019 05:34
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
3
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вкратце: это обычная ленивая инициализация, и jd просто запутался.

В байте 16 создается этот new Object[3]:

16: anewarray     #2                  // class java/lang/Object

Вы можете видеть после этого в 19-35, что он просто сразу копирует локальные переменные в стек (iconst для индекса, aload для ссылки) и затем записывает их в массив (aastore). Следующий байт немедленный равен 36, что является оператором new (только выделение, за которым сразу же следует invokespecial для запуска конструктора).

Это подводит нас к байту 48, который вызывает linkClosureAndJoinPoint. Вы не включили свою таблицу констант, но в 45 ldc_w #327 загружает постоянное значение 69648, так что мы получаем вплоть до точки.around.

Теперь в байте 51 происходит кое-что интересное. Одиночный цепочный вызов, восстановленный jd, теперь прерван. Байт-код загружает поле статической аннотации ajc$anno$10 (не 5, как говорит jd) в стек. Если это поле аннотации не равно нулю (55), то выполнение переходит к 86 (нет операции, используемой в качестве «точки приземления» для прыжка), которая выполняет эту проверку приведения ((SomeAnnotation)), а затем, наконец, фактически вызывает совет.

Пропущенный код (58-82) говорит следующее, что вы узнаете из декомпиляции:

SomeClass.class
    .getDeclaredMethod("doSomething", new Class[] { String.class })
    .getAnnotation(SomeAnnotation.class)

Затем байт 83 сохраняет результат в статическое поле, и выполнение продолжается оттуда.

В терминах Java это именно то, что происходит:

if (cachedAnnotation == null) {
    cachedAnnotation = getAnnotationOnMethodUsingReflection();
}

Байт-код AspectJ здесь очень плотный и чистый (вероятно, оптимизированный вручную, потому что это, вероятно, будет очень горячий код). Либо из-за этого, либо из-за того, что эта логика прерывает вызов связанного метода, jd путается и разделяет проверку на нулевое значение и присваивание.

Ха-ха, извините за ajc$anno$10, я неправильно скопировал строку.

Lycus 09.02.2019 19:04

Другие вопросы по теме