我正在使用 Javassist 在运行时扩展某些类。
在几个地方(在生成代码中)我需要创建 Javassist ConstPool
类的实例。
例如,要将生成的类标记为 synthetic
,我会这样写:
CtClass ctClassToExtend = ... //class to extend
CtClass newCtClass = extend(ctClassToExtend, ...); //method to create a new ctClass extending ctClassToExtend
SyntheticAttribute syntheticAttribute = new SyntheticAttribute(ctClassToExtend.getClassFile().getConstPool()); //creating a synthetic attribute using an instance of ConstPool
newCtClass.setAttribute(syntheticAttribute.getName(), syntheticAttribute.get()); //marking the generated class as synthetic
这是按预期工作的,但我对这是否完全正确存有疑问。具体来说,我的主要问题是:
在这个例子中调用 CtClass.getClassFile().getConstPool()
是获取常量池的正确方法吗?如果不是,在运行时使用 Javassist 创建新类时获取常量池正确实例的一般正确方法是什么?
另外,我对这里幕后发生的事情有点迷茫:为什么我们需要一个常量池来创建合成属性的实例?或者一般来说,任何其他类型的类属性的实例?
感谢任何澄清。
最佳答案
不知道您是否仍然对答案感兴趣,但至少可以帮助其他人 找到这个问题。
首先,给开始创建/修改字节码的每个人一个小建议 并且需要关于 JVM 内部如何工作的更深入的信息,JVM's specification documentation起初可能看起来笨重和可怕,但它是无价的帮助!
Is the call to CtClass.getClassFile().getConstPool() the correct way to get a constant pool in this example?.
是的,是的。每个 Java 类都有一个常量池,所以基本上每次你需要访问常量
对于给定类的池,您可以执行 ctClass.getClassFile().getConstPool()
,但您必须牢记
以下:
在 javassist 中,
CtClass
中的常量池字段是一个实例字段,这意味着如果您有两个CtClass
对象 代表同一个类,你将有两个不同的常量池实例(即使它们代表 实际类文件中的常量池)。修改其中一个CtClass
实例时,您必须使用 关联的常量池实例,以便具有预期的行为。有时您可能没有
CtClass
,而是CtMethod
或CtField
,它们不允许您使用回溯到CtClass
实例,在这种情况下,您可以使用ctMethod.getMethodInfo().getConstPool()
和ctField.getFieldInfo().getConstPool()
以检索正确的常量池。因为我已经提到了
CtMethod
和CtField
,请记住,如果您想向其中任何一个添加属性,则不能通过>ClassFile
对象,但是分别通过MethodInfo
和FieldInfo
。
Why do we need a constant pool to create a instance of a synthetic attribute ?, or in general, of any other kind of class attributes ?
为了回答这个问题,我将开始引用 section 4.4 regarding JVM 7 specs (就像我说的,这个文档很有帮助):
Java virtual machine instructions do not rely on the runtime layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table.
考虑到这一点,我认为阐明这个主题的最好方法是查看类文件转储。我们可以通过运行以下命令来实现:
javap -s -c -p -v SomeClassFile.class
Javap自带java SDK,是分析这一层类的好工具,各个开关的解释
- -s : 打印内部类型签名
- -c : 打印字节码
- -p : 打印所有类成员(方法和字段,包括私有(private)的)
- -v : 冗长,将打印大头钉信息和类常量池
这是我通过 javassist 修改的 test.Test1
类的输出,它在类和 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,这意味着它们是合成的,但是合成符号也必须存在于常量池中(检查 #33)。
关于常量池和类/方法属性的使用的另一个示例是添加到具有运行时保留策略的 someInjectedMethod 的注释。该方法的字节码只有对常量池#32 符号的引用,只有在那里你才知道 注释来自类型 test/TestAnnotationToShowItInConstantTable;
希望您现在对事情有了更清楚的了解。
关于java - 了解 Javassist 中的常量池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12248897/