有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java理解Javassist中的常量池

我正在使用Javassist在运行时扩展某些类。 在一些地方(在生成代码中),我需要创建JavassistConstPool类的实例。 例如,为了将生成的类标记为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创建新类时,获取常量池正确实例的一般正确方法是什么

此外,我对幕后发生的事情也有点迷茫:为什么我们需要一个常量池来创建合成属性的实例?,或者一般来说,任何其他类型的类属性

谢谢你的澄清


共 (1) 个答案

  1. # 1 楼答案

    不知道你是否仍然对答案感兴趣,但至少可以帮助其他人 找到这个问题

    首先,给每个开始创建/修改字节码的人一个小建议 而且需要更多关于JVM内部如何工作的深入信息,JVM's specification documentation一开始可能看起来笨重可怕,但这是非常宝贵的帮助

    Is the call to CtClass.getClassFile().getConstPool() the correct way to get a constant pool in this example?.

    是的。每个Java类都有一个常量池,因此每次需要访问该常量时都会使用basicali 对于给定的类,您可以执行ctClass.getClassFile().getConstPool(),但必须记住 以下:

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

    2. 有时,您可能没有CtClass,而是有一个CtMethod或一个CtField,它们不允许您回溯到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随javasdk一起提供,它是在这个级别分析类的一个很好的工具,解释了每个开关

    • -s:打印内部类型签名
    • -打印字节码
    • -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,这意味着它们是合成的,但合成符号也必须存在于常量池中(请检查#33)

    关于常量池和类/方法属性使用的另一个示例是使用运行时保留策略添加到someInjectedMethod的注释。该方法的字节码只有一个对常量池#32符号的引用,只有在那里您才能了解到这一点 注释来自类型测试/TestAnnotationToShowItInConstantTable

    希望你现在能明白一点