コンストラクターのないクラスは、このクラスのインスタンスを作成する必要があります。 これはJavaでは実行できないため、バイトコードを直接操作する必要があります。 アイデアは、NEWを呼び出すことですが、<init>を呼び出すことではありません。 しかし、困難があります。JVMの仕様では 、これを行うことはできません、
ASMを取り、実験を始めました。
「額」にしようとする
まず、アイデアを直接実装してみましょう。コンストラクタなしでオブジェクトをインスタンス化するメソッドコードを生成します。
MethodVisitor methodVisitor = classVisitor.visitMethod(ACC_PUBLIC | ACC_STATIC, FACTORY_METHOD_NAME, Type.getMethodDescriptor(Type.getObjectType(classToInstantiateInternalName)), null, null); methodVisitor.visitCode(); methodVisitor.visitTypeInsn(NEW, classToInstantiateInternalName); if (invokeObjectConstructor) { methodVisitor.visitInsn(DUP); methodVisitor.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false); } methodVisitor.visitInsn(ARETURN); methodVisitor.visitMaxs(1, 0); methodVisitor.visitEnd();
通常、<init>を呼び出さずに、または別のクラス(この場合はObject)から<init>を呼び出して、2つの生成オプションがあります。 予想通り、これは機能しませんでした。どちらの場合も、2番目の「間違った初期化メソッドの呼び出し」で「スタック上のオブジェクト/配列を見つけることを期待しています」というメッセージ( 初期化されていない同じタイプ )でVerifyErrorを受け取ります。
どこかですでに見た
シリアル化の仕組みの説明を読むと、クラスのコンストラクターは呼び出されず、Serializableを実装していない最初のスーパークラスのデフォルトコンストラクターのみが呼び出されます。 これがまさに私たちが必要とするものです。
ObjectStreamClassのソースコードを詳しく調べると、sun.reflect.ReflectionFactory.newConstructorForSerialization()の形式で魔法を見つけることができます。 使用してみましょう。
Constructor<Object> objCons = Object.class.getConstructor(); Constructor<?> c = sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(noConstructorClass, objCons); Object instance = c.newInstance(); logger.info("Instance: {}", instance);
そしてうまくいきました!
輸入代替
ReflectionFactoryがこれを行う方法を理解し、輸入された技術を国内の現実に再現しようとすることは残っています。 OpenJDKのソースコードを調べた結果、すべての汚い作業を行う特別なクラスが結果として生成されることを理解することができます。 OpenJDKソースで少しシャーマニズムを行った後、バイトコードを取得し、「nevermind」が表示されます。同じコードがありますが、ここでは機能しますが、機能しません。
バイトコード
例外の処理と引数のチェックに関連するガベージがたくさんありますが、実際に機能する行は0、3、24、27です。
public class sun.reflect.GeneratedSerializationConstructorAccessor1はsun.reflect.SerializationConstructorAccessorImplを拡張します{ public sun.reflect.GeneratedSerializationConstructorAccessor1(); コード: 0:aload_0 1:invokespecial#36 //メソッドsun / reflect / SerializationConstructorAccessorImpl。 "<init>" :()V 4:戻る public java.lang.Object newInstance(java.lang.Object [])throws java.lang.reflect.InvocationTargetException; コード: 0:新しい#6 // class com / example / test / classes / NoConstructor 3:二重 4:aload_1 5:ifnull 24 8:aload_1 9:配列長 10:sipush 0 13:if_icmpeq 24 16:新規#22 //クラスjava / lang / IllegalArgumentException 19:デュプ 20:invokespecial#29 //メソッドjava / lang / IllegalArgumentException。 "<init>" :()V 23:投げる 24:invokespecial#12 //メソッドjava / lang / Object。 "<init>" :()V 27:戻り 28:invokespecial#42 //メソッドjava / lang / Object.toString :()Ljava / lang / String; 31:新規#22 //クラスjava / lang / IllegalArgumentException 34:dup_x1 35:スワップ 36:invokespecial#32 //メソッドjava / lang / IllegalArgumentException。 "<init>":(Ljava / lang / String;)V 39:投げる 40:新規#24 //クラスjava / lang / reflect / InvocationTargetException 43:dup_x1 44:スワップ 45:invokespecial#35 //メソッドjava / lang / reflect / InvocationTargetException。 "<init>":(Ljava / lang / Throwable;)V 48:投げる 例外表: fromからターゲットタイプ 0 24 28クラスjava / lang / ClassCastException 0 24 28クラスjava / lang / NullPointerException 24 27 40クラスjava / lang / Throwable }
例外の処理と引数のチェックに関連するガベージがたくさんありますが、実際に機能する行は0、3、24、27です。
それを理解しようとする際に、sun.reflect.SerializationConstructorAccessorImplを開いてそこを見てください:
Javaシリアル化(java.io内)は、クラスをインスタンス化し、そのクラスの最初のシリアル化不可能なスーパークラスの引数なしコンストラクターを呼び出すことができることを期待しています。 これは、VM仕様によると有効な操作ではありません。 (クラスAおよびB、BはAのサブクラスの場合)「new B; invokespecial A()»検証エラーを取得せずに。
他のすべての点で、バイトコードベースのリフレクションフレームワークは、この目的のために再利用できます。 このマーカークラスは元々VMに知られており、その検証とすべてのサブクラスに対して検証が無効にされていましたが、4486457のバグ修正により、リフレクションに関連する動的に生成されるすべてのバイトコードの検証を無効にする必要がありました。 このクラスは、将来のデバッグを容易にするために残されています。
ここに、強力なReflectionFactory魔術のソースがあります。
さて、このクラスから継承します(ちなみに、バグ4486457、MagicAccessorImplから継承することをお勧めします)。同じことをクランクしてみてください。 そして、すべてが機能し、両方のオプションは、Objectからの<init>の呼び出しと、それなしの両方で機能します。 したがって、 初期化されていない型は、実際のエンティティではなく、バイトコード検証の概念であるようです。
すべてのコードはBitbucketで利用可能です。 プロジェクトには、オブジェクトをインスタンス化するための記述されたすべての試行を示すテストがあります。