Я использую плетение во время компиляции 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).
Вот необработанный машинный код 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 генерирует подобную логику.
@chrylis Спасибо за ответ. Я добавил дизассемблирование на случай, если это поможет, но я сам не совсем понимаю это.
Хорошо, это оказалось увлекательной кроличьей норой.




Вкратце: это обычная ленивая инициализация, и 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, я неправильно скопировал строку.
Я подозреваю, что
jdиспытывает трудности с правильным анализом байт-кода, что может стать довольно странным, когда задействован AspectJ. Фактическая разборка метода может быть полезной. Мой WAG будет заключаться в том, что он предназначен для обеспечения запуска загрузки классов.