如何在 _runtime_ 计算 Java class 的反汇编?

How to compute the disassembly of a Java class at _runtime_?

我正在尝试调查 dynamic weaving 是否应用于我的 classes。

首先,这里有一些背景……


要证明是否发生静态摆动是很容易的。你用javap -c MyCoolEntity看一些JavaclassMyCoolEntity.class的反汇编。

在此示例中:MyCoolEntity 是描述 JPA 实体的 class。可以在我们的构建过程中使用插件来"weave"它的字节码。

A getter 表示 JPA 实体关系 将 — 在 非编织 字节码中 — 看起来像一个简单的字段查找:

public java.util.List<com.Whosebug.Friend> getFriends();
  Code:
     0: aload_0
     1: getfield      #222                // Field friends:Ljava/util/List;
     4: areturn

但是weaved字节码将调用委托给一个虚方法,_persistence_get_*():

public java.util.List<com.Whosebug.Friend> getFriends();
  Code:
     0: aload_0
     1: invokevirtual #640                // Method _persistence_get_friends:()Ljava/util/List;
     4: areturn

这个编织的字节码可用于实现惰性查找(按需从数据库中获取相关实体)。


我希望我可以应用相同的逻辑来确定我的应用程序中是否正在发生 动态编织

我的理解是:Java代理在运行时间转换class字节码

有没有一种方法可以访问我的 JPA 实现的 Java 代理在 运行 时间生成的 .class 文件? 否则: 有没有一种方法可以连接到 运行ning JVM 并对其内存中的 class 执行反汇编?


免责声明:我的问题主要与 "how do you grab & disassemble a .class file that is generated at runtime" 有关。可能有 other 方法来回答子问题 "is dynamic weaving occurring?",但这部分不是我问题的主要焦点。 :)

物有所值:我正在使用 EclipseLink 2.5.1 和 TomCat 8.5。

您可以使用 Apache Commons BCEL 并执行如下操作:

package de.scrum_master.Whosebug;

import java.io.IOException;
import java.io.InputStream;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

public class MethodDisassembler {
  public static void main(String[] args) throws IOException {
    Class<?> clazz = MethodDisassembler.class;
    String classAsPath = clazz.getName().replace('.', '/') + ".class";
    try (InputStream classStream = clazz.getClassLoader().getResourceAsStream(classAsPath)) {
      ClassParser classParser = new ClassParser(classStream, classAsPath);
      JavaClass javaClass = classParser.parse();
      for (Method method : javaClass.getMethods())
        System.out.println(method.getCode());
    }
  }

  public void doSomething() {
    sendEmail();
  }

  public void sendEmail() {
    System.out.println("Sending e-mail");
  }
}

控制台日志:

Code(max_stack = 1, max_locals = 1, code_length = 5)
0:    aload_0
1:    invokespecial java.lang.Object.<init>:()V (8)
4:    return

Attribute(s) = 
LineNumber(0, 10)
LocalVariable(start_pc = 0, length = 5, index = 0:de.scrum_master.Whosebug.MethodDisassembler this)
Code(max_stack = 5, max_locals = 12, code_length = 165)
0:    ldc       de.scrum_master.Whosebug.MethodDisassembler (1)
2:    astore_1
3:    new       <java.lang.StringBuilder> (19)
6:    dup
7:    aload_1
8:    invokevirtual java.lang.Class.getName:()Ljava/lang/String; (21)
11:   bipush        46
13:   bipush        47
15:   invokevirtual java.lang.String.replace:(CC)Ljava/lang/String; (27)
18:   invokestatic  java.lang.String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; (33)
21:   invokespecial java.lang.StringBuilder.<init>:(Ljava/lang/String;)V (37)
24:   ldc       ".class" (40)
26:   invokevirtual java.lang.StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; (42)
29:   invokevirtual java.lang.StringBuilder.toString:()Ljava/lang/String; (46)
32:   astore_2
33:   aconst_null
34:   astore_3
35:   aconst_null
36:   astore        %4
38:   aload_1
39:   invokevirtual java.lang.Class.getClassLoader:()Ljava/lang/ClassLoader; (49)
42:   aload_2
43:   invokevirtual java.lang.ClassLoader.getResourceAsStream:(Ljava/lang/String;)Ljava/io/InputStream; (53)
46:   astore        %5
48:   new       <org.apache.bcel.classfile.ClassParser> (59)
51:   dup
52:   aload     %5
54:   aload_2
55:   invokespecial org.apache.bcel.classfile.ClassParser.<init>:(Ljava/io/InputStream;Ljava/lang/String;)V (61)
58:   astore        %6
60:   aload     %6
62:   invokevirtual org.apache.bcel.classfile.ClassParser.parse:()Lorg/apache/bcel/classfile/JavaClass; (64)
65:   astore        %7
67:   aload     %7
69:   invokevirtual org.apache.bcel.classfile.JavaClass.getMethods:()[Lorg/apache/bcel/classfile/Method; (68)
72:   dup
73:   astore        %11
75:   arraylength
76:   istore        %10
78:   iconst_0
79:   istore        %9
81:   goto      #105
84:   aload     %11
86:   iload     %9
88:   aaload
89:   astore        %8
91:   getstatic     java.lang.System.out:Ljava/io/PrintStream; (74)
94:   aload     %8
96:   invokevirtual org.apache.bcel.classfile.Method.getCode:()Lorg/apache/bcel/classfile/Code; (80)
99:   invokevirtual java.io.PrintStream.println:(Ljava/lang/Object;)V (86)
102:  iinc      %9  1
105:  iload     %9
107:  iload     %10
109:  if_icmplt     #84
112:  aload     %5
114:  ifnull        #164
117:  aload     %5
119:  invokevirtual java.io.InputStream.close:()V (92)
122:  goto      #164
125:  astore_3
126:  aload     %5
128:  ifnull        #136
131:  aload     %5
133:  invokevirtual java.io.InputStream.close:()V (92)
136:  aload_3
137:  athrow
138:  astore        %4
140:  aload_3
141:  ifnonnull     #150
144:  aload     %4
146:  astore_3
147:  goto      #162
150:  aload_3
151:  aload     %4
153:  if_acmpeq     #162
156:  aload_3
157:  aload     %4
159:  invokevirtual java.lang.Throwable.addSuppressed:(Ljava/lang/Throwable;)V (97)
162:  aload_3
163:  athrow
164:  return

Exception handler(s) = 
From    To  Handler Type
48  112 125 <Any exception>(0)
38  138 138 <Any exception>(0)

Attribute(s) = 
LineNumber(0, 12), LineNumber(3, 13), LineNumber(33, 14), LineNumber(38, 14), 
LineNumber(48, 15), LineNumber(60, 16), LineNumber(67, 17), LineNumber(91, 18), 
LineNumber(102, 17), LineNumber(112, 19), LineNumber(164, 20)
LocalVariable(start_pc = 0, length = 165, index = 0:java.lang.String[] args)
LocalVariable(start_pc = 3, length = 162, index = 1:java.lang.Class clazz)
LocalVariable(start_pc = 33, length = 132, index = 2:java.lang.String classAsPath)
LocalVariable(start_pc = 48, length = 88, index = 5:java.io.InputStream classStream)
LocalVariable(start_pc = 60, length = 52, index = 6:org.apache.bcel.classfile.ClassParser classParser)
LocalVariable(start_pc = 67, length = 45, index = 7:org.apache.bcel.classfile.JavaClass javaClass)
LocalVariable(start_pc = 91, length = 11, index = 8:org.apache.bcel.classfile.Method method)
LocalVariableTypes(start_pc = 3, length = 162, index = 1:java.lang.Class<?>... clazz)
StackMap((FULL, offset delta=84, locals={(type=Object, class=[Ljava.lang.String;), (type=Object, class=java.lang.Class), (type=Object, class=java.lang.String), (type=Object, class=java.lang.Throwable), (type=Object, class=java.lang.Throwable), (type=Object, class=java.io.InputStream), (type=Object, class=org.apache.bcel.classfile.ClassParser), (type=Object, class=org.apache.bcel.classfile.JavaClass), (type=Bogus), (type=Integer), (type=Integer), (type=Object, class=[Lorg.apache.bcel.classfile.Method;)}), (SAME, offset delta=20), (FULL, offset delta=19, locals={(type=Object, class=[Ljava.lang.String;), (type=Object, class=java.lang.Class), (type=Object, class=java.lang.String), (type=Object, class=java.lang.Throwable), (type=Object, class=java.lang.Throwable), (type=Object, class=java.io.InputStream)}, stack items={(type=Object, class=java.lang.Throwable)}), (CHOP 1, offset delta=10), (SAME_LOCALS_1_STACK, offset delta=1, stack items={(type=Object, class=java.lang.Throwable)}), (SAME, offset delta=11), (SAME, offset delta=11), (CHOP 2, offset delta=1))
Code(max_stack = 1, max_locals = 1, code_length = 5)
0:    aload_0
1:    invokevirtual de.scrum_master.Whosebug.MethodDisassembler.sendEmail:()V (124)
4:    return

Attribute(s) = 
LineNumber(0, 23), LineNumber(4, 24)
LocalVariable(start_pc = 0, length = 5, index = 0:de.scrum_master.Whosebug.MethodDisassembler this)
Code(max_stack = 2, max_locals = 1, code_length = 9)
0:    getstatic     java.lang.System.out:Ljava/io/PrintStream; (74)
3:    ldc       "Sending e-mail" (127)
5:    invokevirtual java.io.PrintStream.println:(Ljava/lang/String;)V (129)
8:    return

Attribute(s) = 
LineNumber(0, 27), LineNumber(8, 28)
LocalVariable(start_pc = 0, length = 9, index = 0:de.scrum_master.Whosebug.MethodDisassembler this)

剩下的估计你自己想好了


更新和免责声明: 抱歉,我忽略了对已检测的 classes 执行此操作的要求,即 classes 已更改在 class 加载期间与磁盘上的原始 class 文件相比。在这种情况下,我建议编写您自己的小 Java 代理并将其放在代理链中的编织代理(无论它是什么,AspectJ 或其他东西)之后,然后使用工具 API 本身或再次使用 BCEL 以查明 class 之前是否已正确检测。

更新 2: Here 您会看到如何按照上面的建议从检测代理转储二进制文件 class。您仍然可以决定是将字节存储在磁盘上还是将它们缓冲在内存中的字节数组中,然后通过 BCEL 通过 ByteArrayInputStream 从那里读取它们。但是,如果您在集成测试之外需要它,这似乎很人为。在生产运行时期间,您可能不想使用它,或者至少只是在启动应用程序时作为快速冒烟测试谨慎使用。