gpt4 book ai didi

java - 了解 Javassist 中的常量池

转载 作者:塔克拉玛干 更新时间:2023-11-01 21:58:57 26 4
gpt4 key购买 nike

我正在使用 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(),但您必须牢记以下:

  1. 在 javassist 中,CtClass 中的常量池字段是一个实例字段,这意味着如果您有两个 CtClass 对象代表同一个类,你将有两个不同的常量池实例(即使它们代表实际类文件中的常量池)。修改其中一个 CtClass 实例时,您必须使用关联的常量池实例,以便具有预期的行为。

  2. 有时您可能没有 CtClass,而是 CtMethodCtField,它们不允许您使用回溯到 CtClass 实例,在这种情况下,您可以使用 ctMethod.getMethodInfo().getConstPool()ctField.getFieldInfo().getConstPool() 以检索正确的常量池。

    因为我已经提到了 CtMethodCtField,请记住,如果您想向其中任何一个添加属性,则不能通过 >ClassFile 对象,但是分别通过MethodInfoFieldInfo

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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com