【问题标题】:Why does AspectJ generate an empty Annotation check?为什么 AspectJ 会生成一个空的 Annotation 检查?
【发布时间】:2019-07-03 05:47:39
【问题描述】:

我正在使用 AspectJ 1.8.8 编译时编织,我有一个这样的块

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

@SomeAnnotation 是通过“Around”建议实现的。

用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

这是来自 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$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 感谢您的回复。我添加了反汇编以防万一,但我自己并不完全理解。
  • 好吧,这竟然是一个迷人的兔子洞。

标签: java aop aspectj jvm-bytecode jd-gui


【解决方案1】:

tl;dr:这是一个正常的延迟初始化,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 的事情感到抱歉,那是我没有正确复制行。
猜你喜欢
  • 1970-01-01
  • 2020-01-28
  • 2019-10-11
  • 1970-01-01
  • 2013-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
相关资源
最近更新 更多