java - 了解 Javassist 中的常量池

标签 java javassist

我正在使用 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/

相关文章:

java - 如何按照放入 int map 的顺序从 hashmap 获取键和值

java - 如何找到文本中复合词的出现

java - 向 Java 类添加字段

java - 使用 Javassist 编译 hello world 类时出现问题

java - 如何查找方法中调用的所有方法(包括 Lamda)?

java - 基于 hibernate 4.2 模式的 Multi-Tenancy + c3p0 连接池

java - 我的数据库中有 2500 万条记录,我想使用 java 检索它

java - 使用 java spring 应用程序进行 SAP RFC 调用

java - 如何使用 javassist 删除方法或构造函数的主体?

java - 使 Java 编译器在类文件中包含符号常量字段引用 - 可能吗?