3

Javassistを使用して、実行時に特定のクラスを拡張しています。ConstPoolいくつかの場所(生成コード内)で、Javassistクラスのインスタンスを作成する必要があります。たとえば、生成されたクラスをとしてマークするために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では、fromの定数プールフィールドCtClassはインスタンスフィールドです。つまりCtClass、同じクラスを表す2つのオブジェクトがある場合、(実際のクラスファイルで定数プールを表す場合でも)定数プールの2つの異なるインスタンスがあります。インスタンスの1つを変更する場合CtClass、期待される動作を実現するには、関連付けられた定数プールインスタンスを使用する必要があります。

  2. インスタンスにバックトレースできないCtClassaCtMethodまたはaがない場合があります。そのような場合は、正しい定数プールを使用して取得できます。CtFieldCtClassctMethod.getMethodInfo().getConstPool()ctField.getFieldInfo().getConstPool()

    とについて説明したので、これらのいずれかに属性を追加する場合は、オブジェクトを介してCtMethodではなく、それぞれを介して行うことができることに注意してください。CtFieldClassFileMethodInfoFieldInfo

合成属性のインスタンス、または一般に他の種類のクラス属性のインスタンスを作成するために定数プールが必要なのはなぜですか?

この質問に答えるために、JVM 7の仕様に関するセクション4.4の引用を開始します(私が言ったように、このドキュメントは非常に役立ちます)。

Java仮想マシンの命令は、クラス、インターフェース、クラスインスタンス、または配列のランタイムレイアウトに依存しません。代わりに、命令はconstant_poolテーブルのシンボリック情報を参照します。

このことを念頭に置いて、この問題に光を当てる最善の方法は、クラスファイルのダンプを調べることだと思います。これは、次のコマンドを実行することで実現できます。

javap -s -c -p -v SomeClassFile.class

JavapにはjavaSDKが付属しており、このレベルでクラスを分析するための優れたツールであり、各スイッチの説明です。

  • -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 に答える