web-dev-qa-db-ja.com

リフレクションを使用して非表示クラスにアクセスする

私はリフレクションを使用して、非表示のクラス、別名パッケージプライベートクラスのインスタンスを取得しようとしています。修飾子を切り替えて公開し、Class.forNameを使用してアクセスする方法があるかどうか疑問に思いました。今私がそれを試みるとき、それは私がそれをすることができないと言うのを止めます。残念ながら、setAccesibleクラスのClassメソッドはありません。

45
Josh Sobel

ネストされたクラス-他のクラス内で定義されたクラス(静的および非静的クラスを含む)
内部クラス-非静的なネストされたクラス(内部クラスのインスタンスが存在するには外部クラスのインスタンスが必要です)

ネストされていない(トップレベル)クラス

あなたの質問に基づいて、アクセスしたいコンストラクタはパブリックではないことがわかります。したがって、クラスは次のようになります(Aクラスは、私たちのものとは異なるパッケージにあります)

_package package1;

public class A {
    A(){
        System.out.println("this is non-public constructor");
    }
}
_

このクラスのインスタンスを作成するには、呼び出すコンストラクターにアクセスして、アクセス可能にする必要があります。完了したら、Constructor#newInstance(arguments)を使用してインスタンスを作成できます。

_Class<?> c = Class.forName("package1.A");
//full package name --------^^^^^^^^^^
//or simpler without Class.forName:
//Class<package1.A> c = package1.A.class;

//In our case we need to use
Constructor<?> constructor = c.getDeclaredConstructor();
//note: getConstructor() can return only public constructors
//so we needed to search for any Declared constructor

//now we need to make this constructor accessible
constructor.setAccessible(true);//ABRACADABRA!

Object o = constructor.newInstance();
_

ネストされたクラスと内部クラス

_Class.forName_でネストされた(静的および非静的)クラスにアクセスする場合は、構文を使用する必要があります。

_Class<?> clazz = Class.forName("package1.Outer$Nested");
_

_Outer$Nested_は、NestedクラスがOuterクラス内で宣言されていることを示します。ネストされたクラスはメソッドに非常によく似ており、外部クラスのすべてのメンバー(プライベートクラスを含む)にアクセスできます。

ただし、存在する内部クラスのインスタンスには外部クラスのインスタンスが必要であることを覚えておく必要があります。通常、次の方法で作成します。

_Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
_

innerクラスの各インスタンスがその外部クラスに関する情報を持っていることがわかります(その外部インスタンスへの参照は_this$0_フィールドに格納されています。詳細情報: IntelliJの名前「this $ 0」IDEA Javaのデバッグ中?

Constructor#newInstance()を使用してInnerクラスのインスタンスを作成するとき、Outerクラスのインスタンスへの最初の引数参照として渡す必要があります(outer.new Inner()動作をシミュレートするため) 。

以下に例を示します。

package1に

_package package1;

public class Outer {
    class Inner{
        Inner(){
            System.out.println("non-public constructor of inner class");
        }
    }
}
_

package2に

_package package2;

import package1.Outer;
import Java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {

        Outer outerObject = new Outer();

        Class<?> innerClazz = Class.forName("package1.Outer$Inner");

        // constructor of inner class as first argument need instance of
        // Outer class, so we need to select such constructor
        Constructor<?> constructor = innerClazz.getDeclaredConstructor(Outer.class);

        //we need to make constructor accessible 
        constructor.setAccessible(true);

        //and pass instance of Outer class as first argument
        Object o = constructor.newInstance(outerObject);

        System.out.println("we created object of class: "+o.getClass().getName());

    }
}
_

静的にネストされたクラス

静的にネストされたクラスのインスタンスは、外部クラスのインスタンスを必要としません(静的であるため)。したがって、彼らの場合、最初の引数として_Outer.class_を持つコンストラクターを探す必要はありません。そして、外部クラスのインスタンスを最初の引数として渡す必要はありません。言い換えると、コードはネストされていない(トップレベル)クラスのコードと同じになります(Class.forName()に_$Nested_構文を追加する必要がある場合を除きます)。

57
Pshemo

Class.forNameは動作するはずです。クラスが"package.access"セキュリティプロパティのパッケージ階層リスト内にある場合、適切な特権(通常はすべてのアクセス許可、またはセキュリティマネージャーを持たない)で操作を実行する必要があります。

Class.newInstanceを使用しようとしている場合は、使用しないでください。 Class.newInstanceは例外の処理が不十分です。代わりにConstructorを取得し、その上でnewInstanceを呼び出します。例外トレースなしでは、何に問題があるのか​​を確認することは困難です。

相変わらず、反射のすべてではないがほとんどの使用は悪い考えです。

値が最新バージョンでnullの場合、オブジェクトの古いバージョンからフィールドの値をコピーする必要がありました。これら2つのオプションがありました。

コアJava:

for (Field f : object.getClass().getSuperclass().getDeclaredFields()) {
    f.setAccessible(true);
  System.out.println(f.getName());
  if (f.get(object) == null){
    f.set(object, f.get(oldObject));
  }
}

Spring [org.springframework.beans.BeanWrapper]の使用:

BeanWrapper bw = new BeanWrapperImpl(object);
PropertyDescriptor[] data = bw.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : data) {
  System.out.println(propertyDescriptor.getName());
  Object propertyValue = bw.getPropertyValue(propertyDescriptor.getName());
  if(propertyValue == null )
    bw.setPropertyValue( new PropertyValue(propertyDescriptor.getName(),"newValue"));
}
0
Quest_for_java

Manifold's@ Jailbreakを使用して、タイプセーフな直接Javaリフレクション:

_Foo foo = new @Jailbreak Foo();

public class Foo {
    Foo() {...}

    private void yodog() {...}
}
_

ここで_@Jailbreak_を使用すると、コンパイラはパブリックのようにコンストラクタを型安全に解決できます。一方、Manifoldは内部で効率的なリフレクションコードを生成します。

さらに、_@Jailbreak_を使用して、非表示のクラスにアクセスして構築できます。

_com.abc. @Jailbreak Bar bar = new com.abc. @Jailbreak Bar();

package com.abc;
// package-private class
class Bar {
    Bar() {...}
}
_

隠されたクラスアクセスの場合、Javaの注釈文法では、パッケージとは別にクラスに注釈を付ける必要があります。

より一般的には、あらゆるタイプのリフレクションに_@Jailbreak_を使用できます。

_@Jailbreak Foo foo = new Foo();
foo.yodog();
_

_@Jailbreak_は、Fooの階層内のすべてのメンバーに直接アクセスするために、コンパイラーでfooローカル変数のロックを解除します。

同様に、jailbreak()拡張メソッドを1回限りの使用に使用できます。

_foo.jailbreak().yodog();
_

jailbreak()メソッドを介して、Fooの階層内の任意のメンバーにアクセスできます。

マニホールド の詳細をご覧ください。

0
Scott

私たちは最近、リフレクションを介してプライベートフィールド、メソッド、内部クラスにアクセスするのに役立つライブラリをリリースしました: BoundBox

のようなクラスの場合

_public class Outer {
    private static class Inner {
        private int foo() {return 2;}
    }
}
_

次のような構文を提供します。

_Outer outer = new Outer();
Object inner = BoundBoxOfOuter.boundBox_new_Inner();
new BoundBoxOfOuter.BoundBoxOfInner(inner).foo();
_

BoundBoxクラスを作成するために必要なことは、@BoundBox(boundClass=Outer.class)を記述することだけで、BoundBoxOfOuterクラスは即座に生成されます。

0
Snicolas