web-dev-qa-db-ja.com

jdk12のJava.lang.reflect.Fieldsの宣言されたフィールドを取得する

Java8では、次を使用してクラスJava.lang.reflect.Fieldsのフィールドにアクセスできました。

Field.class.getDeclaredFields();

Java12(Java9以降)では、これは空の配列のみを返します。これでも変わらない

--add-opens Java.base/Java.lang.reflect=ALL-UNNAMED

セットする。

これを達成する方法はありますか? (これは悪い考えかもしれないという事実から、反射によるjunitテスト中にコードの「静的なfinal」フィールドを変更できるようにしたいのです。これは、Java8で「修飾子」を変更することで可能になりました

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(myfield, myfield.getModifiers() & ~Modifier.FINAL);

15
Henning

これがJava 12で機能しない理由は JDK-8210522 が原因です。このCSRは次のように述べています:

概要

コアリフレクションには、セキュリティと整合性の影響を受けるフィールドとメソッドをクラスgetXXXField(s)とgetXXXMethod(s)から隠すフィルタリングメカニズムがあります。フィルタリングメカニズムは、System.securityやClass.classLoaderなどのセキュリティの重要なフィールドを非表示にするために、いくつかのリリースで使用されています。

このCSRは、フィルターを拡張して、Java.lang.reflectとJava.lang.invokeの多くの非常に機密性の高いクラスからフィールドを隠すことを提案しています。

問題

Java.lang.reflectおよびJava.lang.invokeパッケージのクラスの多くには、プライベートフィールドがあり、直接アクセスすると、ランタイムが低下したり、VMがクラッシュしたりします。理想的には、Java.baseのクラスのすべての非パブリック/非保護フィールドは、コアリフレクションによってフィルタリングされ、Unsafe APIを介して読み取り/書き込みできなくなりますが、現時点ではこれに近づいていません。その間、フィルタリングメカニズムはバンドエイドとして使用されます。

解決

フィルターを次のクラスのすべてのフィールドに拡張します。

Java.lang.ClassLoader
Java.lang.reflect.AccessibleObject
Java.lang.reflect.Constructor
Java.lang.reflect.Field
Java.lang.reflect.Method

ルックアップクラスとアクセスモードに使用されるJava.lang.invoke.MethodHandles.Lookupのプライベートフィールド。

仕様

仕様の変更はありません。これは、Java.base以外では依存しない非公開/非保護フィールドのフィルタリングです。どのクラスもシリアライズ可能ではありません。

基本的に、それらはJava.lang.reflect.Fieldのフィールドをフィルターで除外するため、現在試みているようにそれらを悪用することはできません。必要なことを行う別の方法を見つける必要があります。 Eugeneによる回答 には、少なくとも1つのオプションがあるようです。


:上記のCSRは、Java.base内の内部コードへのすべてのリフレクトアクセスを防ぐことが最終的な目標であることを示していますモジュール。ただし、このフィルタリングメカニズムはCore Reflection APIにのみ影響を与えるようで、Invoke APIを使用することで回避できます。 2つのAPIの関係が正確にわからないので、これが望ましくない動作である場合-静的な最終フィールドを変更することの怪しさを超えて-誰かが バグレポートを送信 (既存の1つ目)。つまり、以下のハックを自己責任で使用してください;最初に必要なことを行う別の方法を見つけてください。


そうは言っても、少なくともOpenJDK 12.0.1では、Java.lang.invoke.VarHandleを使用して、modifiersフィールドにハッキングできるようです。

import Java.lang.invoke.MethodHandles;
import Java.lang.invoke.VarHandle;
import Java.lang.reflect.Field;
import Java.lang.reflect.Modifier;

public final class FieldHelper {

    private static final VarHandle MODIFIERS;

    static {
        try {
            var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
            MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static void makeNonFinal(Field field) {
        int mods = field.getModifiers();
        if (Modifier.isFinal(mods)) {
            MODIFIERS.set(field, mods & ~Modifier.FINAL);
        }
    }

}

以下は、上記を使用して、ArrayList内の静的なfinal EMPTY_ELEMENTDATAフィールドを変更します。このフィールドは、ArrayList0の容量で初期化されるときに使用されます。最終結果は、実際に要素を追加せずに作成されたArrayList contains要素です。

import Java.util.ArrayList;

public class Main {

    public static void main(String[] args) throws Exception {
        var newEmptyElementData = new Object[]{"Hello", "World!"};
        updateEmptyElementDataField(newEmptyElementData);

        var list = new ArrayList<>(0);

        // toString() relies on iterator() which relies on size
        var sizeField = list.getClass().getDeclaredField("size");
        sizeField.setAccessible(true);
        sizeField.set(list, newEmptyElementData.length);

        System.out.println(list);
    }

    private static void updateEmptyElementDataField(Object[] array) throws Exception {
        var field = ArrayList.class.getDeclaredField("EMPTY_ELEMENTDATA");
        FieldHelper.makeNonFinal(field);
        field.setAccessible(true);
        field.set(null, array);
    }

}

出力:

[Hello, World!]

必要に応じて--add-opensを使用します。

17
Slaw

できません。これは意図的に行われた変更です。

たとえば、PowerMockを使用すると、@PrepareForTest-テスト目的で使用する場合は、内部では javassist (バイトコード操作)を使用します。これは、コメント内のバグが示唆するとおりです。

つまり、Java-12-バニラJava経由でアクセスする方法はありません。

7
Eugene