为什么 AspectJ 会生成一个空的 Annotation 检查?
Why does AspectJ generate an empty Annotation check?
我正在使用 AspectJ 1.8.8 编译时织入并且我有一个像这样的块
@SomeAnnotation(value="someValue")
public List doSomething(String someArg) {
...
}
其中 @SomeAnnotation
是通过 "Around" 建议实现的。
用JD-GUI查看字节码,生成的代码如下(略有格式化):
public class SomeClass {
private static Annotation ajc$anno;
...
@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;
if (tmp56_53 == null) {
tmp56_53;
}
return (List)new SomeClass.AjcClosure11(arrayOfObject).linkClosureAndJoinPoint(69648).around(tmp56_53, (SomeAnnotation)(ajc$anno = SomeClass.class.getDeclaredMethod("doSomething", new Class[] { String.class }).getAnnotation(SomeAnnotation.class)));
}
}
我想知道为什么那个条件 (if (tmp56_53...)
) 甚至存在,因为它似乎什么都不做(而且在语法上也不正确 Java?也许是因为这是由 ajc 生成的?)。我对此很好奇,因为它在覆盖工具 (JaCoCo) 中导致 "branch misses"。
编辑 1
这是来自 javap 的原始 Java 机器代码:
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: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: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会产生这样的逻辑。
tl;dr: 这是一个正常的惰性初始化,jd
只是糊涂了。
字节 16 是创建 new Object[3]
:
的地方
16: anewarray #2 // class java/lang/Object
之后可以看到19-35就是直接把局部变量复制到栈上(iconst
为索引,aload
为引用),然后写入到数组(aastore
)。 立即下一个字节是36,也就是new
运算符(只是分配,然后紧接着是invokespecial
到运行的构造函数) .
这将我们带到字节 48,它调用 linkClosureAndJoinPoint
。您没有包含常量 table,但在 45 ldc_w #327
加载常量值 69648,因此我们 达到 [=22= 的点 ].
现在在字节 51 处发生了一些有趣的事情。jd
重构的单链调用现在被中断了。字节码将静态注释字段 ajc$anno
(不是 5,如 jd
所说)加载到堆栈上。如果该注释字段不为空 (55),则执行跳转到 86(一个空操作,用作跳转的 "landing point"),执行强制转换检查 ((SomeAnnotation)
) ,然后最后实际上调用了建议。
跳过的代码 (58-82) 是这样说的,您将从反编译中识别出来:
SomeClass.class
.getDeclaredMethod("doSomething", new Class[] { String.class })
.getAnnotation(SomeAnnotation.class)
然后字节 83 将结果存储到静态字段中,并从那里继续执行。
在 Java 术语中,这正是正在发生的事情:
if (cachedAnnotation == null) {
cachedAnnotation = getAnnotationOnMethodUsingReflection();
}
AspectJ 的字节码在这里非常紧凑和干净(可能是手动优化的,因为这可能是非常热门的代码)。正因为如此或因为该逻辑中断了链式方法调用,jd
变得混乱并拆分了 null 检查和赋值。
我正在使用 AspectJ 1.8.8 编译时织入并且我有一个像这样的块
@SomeAnnotation(value="someValue")
public List doSomething(String someArg) {
...
}
其中 @SomeAnnotation
是通过 "Around" 建议实现的。
用JD-GUI查看字节码,生成的代码如下(略有格式化):
public class SomeClass {
private static Annotation ajc$anno;
...
@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;
if (tmp56_53 == null) {
tmp56_53;
}
return (List)new SomeClass.AjcClosure11(arrayOfObject).linkClosureAndJoinPoint(69648).around(tmp56_53, (SomeAnnotation)(ajc$anno = SomeClass.class.getDeclaredMethod("doSomething", new Class[] { String.class }).getAnnotation(SomeAnnotation.class)));
}
}
我想知道为什么那个条件 (if (tmp56_53...)
) 甚至存在,因为它似乎什么都不做(而且在语法上也不正确 Java?也许是因为这是由 ajc 生成的?)。我对此很好奇,因为它在覆盖工具 (JaCoCo) 中导致 "branch misses"。
编辑 1
这是来自 javap 的原始 Java 机器代码:
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: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: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会产生这样的逻辑。
tl;dr: 这是一个正常的惰性初始化,jd
只是糊涂了。
字节 16 是创建 new Object[3]
:
16: anewarray #2 // class java/lang/Object
之后可以看到19-35就是直接把局部变量复制到栈上(iconst
为索引,aload
为引用),然后写入到数组(aastore
)。 立即下一个字节是36,也就是new
运算符(只是分配,然后紧接着是invokespecial
到运行的构造函数) .
这将我们带到字节 48,它调用 linkClosureAndJoinPoint
。您没有包含常量 table,但在 45 ldc_w #327
加载常量值 69648,因此我们 达到 [=22= 的点 ].
现在在字节 51 处发生了一些有趣的事情。jd
重构的单链调用现在被中断了。字节码将静态注释字段 ajc$anno
(不是 5,如 jd
所说)加载到堆栈上。如果该注释字段不为空 (55),则执行跳转到 86(一个空操作,用作跳转的 "landing point"),执行强制转换检查 ((SomeAnnotation)
) ,然后最后实际上调用了建议。
跳过的代码 (58-82) 是这样说的,您将从反编译中识别出来:
SomeClass.class
.getDeclaredMethod("doSomething", new Class[] { String.class })
.getAnnotation(SomeAnnotation.class)
然后字节 83 将结果存储到静态字段中,并从那里继续执行。
在 Java 术语中,这正是正在发生的事情:
if (cachedAnnotation == null) {
cachedAnnotation = getAnnotationOnMethodUsingReflection();
}
AspectJ 的字节码在这里非常紧凑和干净(可能是手动优化的,因为这可能是非常热门的代码)。正因为如此或因为该逻辑中断了链式方法调用,jd
变得混乱并拆分了 null 检查和赋值。