私はサードパーティのJAR
でデザインが良くないクラスを持っています、そして私はそのプライベートフィールドの一つにアクセスする必要があります。例えば、なぜ私がプライベートフィールドを選択する必要があるのですか?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
stuffIWant
の値を得るためにどうやってリフレクションを使うことができますか?
プライベートフィールドにアクセスするには、それらをクラスの宣言済みフィールドから取得してからアクセス可能にする必要があります。
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
EDIT:aperkinsでコメントされているとおり、両方ともフィールドにアクセスし、アクセス可能として設定ただし、注意が必要な唯一のチェック済みの例外は上記のとおりですが、値を取得するとすべてException
sがスローされます。
宣言されたフィールドに対応しない名前でフィールドを要求した場合、NoSuchFieldException
がスローされます。
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
フィールドがアクセスできない場合(たとえば、フィールドが非公開で、f.setAccessible(true)
行を省略してアクセスできない場合など)、IllegalAccessException
がスローされます。
スローされる可能性のあるRuntimeException
sは、SecurityException
s(JVMのSecurityManager
でフィールドのアクセシビリティを変更できない場合)、またはフィールドのクラスの型ではないオブジェクトでフィールドにアクセスしようとした場合はIllegalArgumentException
sのいずれかです。
f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
Apache commons-lang3から FieldUtils
を試してください。
FieldUtils.readField(object, fieldName, true);
あなたの問題を解決する唯一の方法はリフレクションではありません(それはプライベートな機能/クラス/コンポーネントの振る舞いにアクセスすることです)。
別の解決策は、.jarからクラスを抽出し、(say) Jode または Jad を使って逆コンパイルし、フィールドを変更することです。 (またはアクセサを追加して)元の.jarに対して再コンパイルします。次に、新しい.classをクラスパスの.jar
の前に置くか、またはそれを.jar
に再挿入します。 (jarユーティリティを使用すると、既存の.jarを抽出して再挿入できます)
後述するように、これは単にフィールドにアクセスしたり変更したりするのではなく、プライベート状態にアクセスし変更するより広い問題を解決します。
これはもちろん.jar
が署名されていないことを必要とします。
まだ言及されていないもう1つのオプション:useGroovy。 Groovyでは、言語設計の副作用としてプライベートインスタンス変数にアクセスできます。あなたがフィールドのゲッターを持っているかどうかに関係なく、あなたはただ使うことができます
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
JavaでのReflectionを使用すると、あるクラスのすべてのprivate/public
フィールドおよびメソッドに別のクラスにアクセスできます。Oracle/に従って documentation セクションの欠点彼らはそれをお勧めします:
"リフレクションを使用すると、プライベートフィールドやメソッドへのアクセスなど、リフレクティブではないコードでは無効な操作をコードで実行できるため、リフレクションを使用すると予期しない副作用が発生し、反射的なコードは抽象化を破るので、プラットフォームのアップグレードによって動作が変わる可能性があります。」
以下は、Reflectionの基本概念を示すためのコードの抜粋です。
リフレクション1
public class Reflection1{
private int i = 10;
public void methoda()
{
System.out.println("method1");
}
public void methodb()
{
System.out.println("method2");
}
public void methodc()
{
System.out.println("method3");
}
}
Reflection2.Java
import Java.lang.reflect.Field;
import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
public class Reflection2{
public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Method[] mthd = Reflection1.class.getMethods(); // for axis the methods
Field[] fld = Reflection1.class.getDeclaredFields(); // for axis the fields
// Loop for get all the methods in class
for(Method mthd1:mthd)
{
System.out.println("method :"+mthd1.getName());
System.out.println("parametes :"+mthd1.getReturnType());
}
// Loop for get all the Field in class
for(Field fld1:fld)
{
fld1.setAccessible(true);
System.out.println("field :"+fld1.getName());
System.out.println("type :"+fld1.getType());
System.out.println("value :"+fld1.getInt(new Reflaction1()));
}
}
}
それが役立つことを願っています。
Oxbow_lakesが述べているように、リフレクションを使用してアクセス制限を回避することができます(SecurityManagerが許可してくれると仮定して)。
とは言っても、このクラスがあまりにもひどく設計されているためにこのようなハッカーに頼るのであれば、代替手段を探すべきです。このちょっとしたハックで、今は数時間節約できるかもしれませんが、それはいくらかかりますか。
バイトコードを直接変更するには、Soot Java Optimizationフレームワークを使用してください。 http://www.sable.mcgill.ca/soot/
Sootは完全にJavaで書かれており、新しいJavaバージョンで動作します。
次のことを行う必要があります。
private static Field getField(Class<?> cls, String fieldName) {
for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
try {
final Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (final NoSuchFieldException e) {
// Try parent
} catch (Exception e) {
throw new IllegalArgumentException(
"Cannot access field " + cls.getName() + "." + fieldName, e);
}
}
throw new IllegalArgumentException(
"Cannot find field " + cls.getName() + "." + fieldName);
}
リフレクションについてのもう一つの注意:私はいくつかの特別な場合に、同じ名前を持ついくつかのクラスが異なるパッケージに存在するとき、トップアンサーで使用されるリフレクションがオブジェクトから正しいクラスを選択できないかもしれないことを観察しました。そのため、オブジェクトのpackage.classが何であるかがわかっている場合は、次のようにプライベートフィールドの値にアクセスすることをお勧めします。
org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);
(これは私のために働いていなかった例のクラスです)
直接の型保証されたJavaリフレクションには、 Manifoldの@ JailBreakを使用できます。
@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;
public class Foo {
private String stuffIWant;
}
@JailBreak
は、foo
の階層内のすべてのメンバーに直接アクセスできるように、コンパイラ内のFoo
ローカル変数をロック解除します。
同様に、jailbreak()拡張メソッドを1回限りの使用に使用できます。
foo.jailbreak().stuffIWant = "123";
jailbreak()
メソッドを通して、あなたはFoo
の階層構造のどのメンバーにもアクセスできます。
どちらの場合も、パブリックフィールドの場合と同様に、コンパイラはフィールドアクセスを型安全に解決します。一方、Manifoldは、フードの下で効率的なリフレクションコードを生成します。
マニホールド についてもっと詳しく知る。
Springを使用している場合、 ReflectionTestUtils は最小限の労力でここで役立ついくつかの便利なツールを提供します。これは「単体テストおよび統合テストのシナリオで使用するためのもの」として記述されています。 ReflectionUtils という名前の類似クラスもありますが、これは「内部使用のみを目的としています」と記述されています。 - これが何を意味するのかについての解釈については この答え を見てください。
投稿された例に対処するには
Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");