不知道您是否仍然对答案感兴趣,但至少可以帮助其他发现此问题的人。
首先,对开始创建/修改字节码并需要更深入了解 JVM 内部工作原理的每个人的一个小建议,JVM 的规范文档起初可能看起来庞大而可怕,但它是无价的帮助!
在这个例子中,调用 CtClass.getClassFile().getConstPool() 是获取常量池的正确方法吗?
是的。每个 Java 类都有一个常量池,因此基本上每次您需要访问给定类的常量池时,您都可以这样做ctClass.getClassFile().getConstPool()
,尽管您必须牢记以下几点:
在 javassist 中,常量池字段 fromCtClass
是一个实例字段,这意味着如果您有两个CtClass
对象代表同一个类,您将有两个不同的常量池实例(即使它们代表实际类文件中的常量池)。修改其中一个CtClass
实例时,您必须使用关联的常量池实例才能获得预期的行为。
有时您可能没有,CtClass
而是 aCtMethod
或 aCtField
不允许您回溯到CtClass
实例,在这种情况下,您可以使用ctMethod.getMethodInfo().getConstPool()
并ctField.getFieldInfo().getConstPool()
检索正确的常量池。
既然我提到了CtMethod
and CtField
,请记住,如果你想为其中的任何一个添加属性,它不能通过ClassFile
Object ,而是分别通过MethodInfo
and FieldInfo
。
为什么我们需要一个常量池来创建合成属性的实例?或者一般来说,任何其他类型的类属性?
为了回答这个问题,我将开始引用关于 JVM 7 规范的第 4.4 节(就像我说的,这个文档很有帮助):
Java 虚拟机指令不依赖于类、接口、类实例或数组的运行时布局。相反,指令引用常量池表中的符号信息。
考虑到这一点,我认为了解这个主题的最佳方法是查看类文件转储。我们可以通过运行以下命令来实现:
javap -s -c -p -v SomeClassFile.class
javap自带java SDK,是分析这个层次类的好工具,各开关的解释
- -s :打印内部类型签名
- -c :打印字节码
- -p :打印所有类成员(方法和字段,包括私有的)
- -v :详细,将打印大头钉信息和类常量池
test.Test1
这是我通过 javassist 修改的类的输出,以便在类和injectedMethod
Classfile /C:/development/testProject/test/Test1.class
Last modified 29/Nov/2012; size 612 bytes
MD5 checksum 858c009090bfb57d704b2eaf91c2cb75
Compiled from "Test1.java"
public class test.Test1
SourceFile: "Test1.java"
Synthetic: true
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // test/Test1
#2 = Utf8 test/Test1
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ltest/Test1;
#14 = Utf8 SourceFile
#15 = Utf8 Test1.java
#16 = Utf8 someInjectedMethod
#17 = Utf8 java/lang/System
#18 = Class #17 // java/lang/System
#19 = Utf8 out
#20 = Utf8 Ljava/io/PrintStream;
#21 = NameAndType #19:#20 // out:Ljava/io/PrintStream;
#22 = Fieldref #18.#21 // java/lang/System.out:Ljava/io/PrintStream;
#23 = Utf8 injection example
#24 = String #23 // injection example
#25 = Utf8 java/io/PrintStream
#26 = Class #25 // java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
#29 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#30 = Methodref #26.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#31 = Utf8 RuntimeVisibleAnnotations
#32 = Utf8 Ltest/TestAnnotationToShowItInConstantTable;
#33 = Utf8 Synthetic
{
public com.qubit.augmentation.test.Test1();
Signature: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/Test1;
protected void someInjectedMethod();
Signature: ()V
flags: ACC_PROTECTED
Code:
stack=2, locals=1, args_size=1
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #24 // String injection example
5: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
RuntimeVisibleAnnotations:
0: #32()
Synthetic: true
}
请注意,类和方法都具有属性Synthetic: true,这意味着它们是 Synthetic 但是,您的合成符号也必须存在于常量池中(检查 #33)。
关于使用常量池和类/方法属性的另一个示例是使用运行时保留策略添加到 someInjectedMethod 的注释。该方法的字节码只有对常量池#32 符号的引用,并且只有在那里你才知道注释来自类型 test/TestAnnotationToShowItInConstantTable;
希望现在对你来说事情变得更清楚了。