3

我正在使用 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 创建新类时,获取正确的常量池实例的一般正确方法是什么?

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

感谢您的任何澄清。

4

1 回答 1

9

不知道您是否仍然对答案感兴趣,但至少可以帮助其他发现此问题的人。

首先,对开始创建/修改字节码并需要更深入了解 JVM 内部工作原理的每个人的一个小建议,JVM 的规范文档起初可能看起来庞大而可怕,但它是无价的帮助!

在这个例子中,调用 CtClass.getClassFile().getConstPool() 是获取常量池的正确方法吗?

是的。每个 Java 类都有一个常量池,因此基本上每次您需要访问给定类的常量池时,您都可以这样做ctClass.getClassFile().getConstPool(),尽管您必须牢记以下几点:

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

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

    既然我提到了CtMethodand CtField,请记住,如果你想为其中的任何一个添加属性,它不能通过ClassFileObject ,而是分别通过MethodInfoand 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;

希望现在对你来说事情变得更清楚了。

于 2012-11-29T08:45:19.343 回答