サードパーティのクラスの設計が不十分で、そのプライベートフィールドJAR
の1つにアクセスする必要があります。たとえば、なぜプライベートフィールドを選択する必要があるのですか?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
リフレクションを使用しての値を取得するにはどうすればよいstuffIWant
ですか?
サードパーティのクラスの設計が不十分で、そのプライベートフィールドJAR
の1つにアクセスする必要があります。たとえば、なぜプライベートフィールドを選択する必要があるのですか?
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
編集:aperkinsによってコメントされているように、フィールドへのアクセス、アクセス可能としての設定、および値の取得の両方でsがスローされる可能性がありますが、注意する必要がException
ある唯一のチェック済み例外は上記でコメントされています。
NoSuchFieldException
宣言されたフィールドに対応しない名前でフィールドを要求した場合、がスローされます。
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
IllegalAccessException
フィールドにアクセスできない場合(たとえば、フィールドがプライベートで、行を見逃してアクセスできるようになっていない場合)にスローされますf.setAccessible(true)
。
RuntimeException
スローされる可能性のあるsは、s SecurityException
(JVMSecurityManager
がフィールドのアクセシビリティを変更できない場合)、またはIllegalArgumentException
s(フィールドのクラスのタイプではないオブジェクトのフィールドにアクセスしようとする場合)のいずれかです。
f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
FieldUtils
apache commons-lang3から試してください:
FieldUtils.readField(object, fieldName, true);
問題を解決する方法はリフレクションだけではありません(クラス/コンポーネントのプライベート機能/動作にアクセスすることです)
別の解決策は、.jarからクラスを抽出し、(たとえば)JodeまたはJadを使用して逆コンパイルし、フィールドを変更(またはアクセサーを追加)して、元の.jarに対して再コンパイルすることです。次に、新しい.classを.jar
クラスパスのの前に置くか、。に再挿入し.jar
ます。(jarユーティリティを使用すると、既存の.jarを抽出して再挿入できます)
以下で説明するように、これにより、単にフィールドにアクセス/変更するのではなく、プライベート状態にアクセス/変更するというより広い問題が解決されます。
.jar
もちろん、これには署名しないことが必要です。
まだ言及されていないもう1つのオプション:Groovyを使用します。Groovyを使用すると、言語の設計の副作用としてプライベートインスタンス変数にアクセスできます。フィールドのゲッターを持っているかどうかに関係なく、あなたはただ使うことができます
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
JavaでReflectionを使用すると、あるクラスのすべてのprivate/public
フィールドとメソッドに別のクラスにアクセスできます。ただし、 Oracleの ドキュメント によると、セクションの欠点として、次のことが推奨されています。
「リフレクションを使用すると、プライベートフィールドやメソッドへのアクセスなど、非リフレクティブコードでは違法となる操作をコードで実行できるため、リフレクションを使用すると、予期しない副作用が発生し、コードが機能しなくなり、移植性が損なわれる可能性があります。リフレクティブコード抽象化を破るため、プラットフォームのアップグレードによって動作が変わる可能性があります。」
これは、 Reflectionの基本的な概念を示すための次のコードスナップです。
Reflection1.java
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最適化フレームワークを使用して、バイトコードを直接変更します。 http://www.sable.mcgill.ca/soot/
Sootは完全にJavaで記述されており、新しいJavaバージョンで動作します。
Springを使用する場合:
テストのコンテキストでは、ReflectionTestUtilsは、最小限の労力でここで役立ついくつかの便利なツールを提供します。「ユニットおよび統合テストのシナリオで使用するため」と説明されています。
テスト以外のコンテキストでは、 ReflectionUtilsという名前の同様のクラスもありますが、これは「内部使用のみを目的としています」と説明されています。これが何を意味するかについては、この回答を参照してください。
元の投稿の例に対処するには:
Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
次のことを行う必要があります。
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);
}
そのためにjOORを使用できます。
class Foo {
private final String value = "ABC";
}
class Bar {
private final Foo foo = new Foo();
public String value() {
return org.joor.Reflect
.on(this.foo)
.field("value")
.get();
}
}
class BarTest {
@Test
void accessPrivateField() {
Assertions.assertEquals(new Bar().value(), "ABC");
}
}
Java9では変数ハンドルが導入されました。それらを使用して、クラスのプライベートフィールドにアクセスできます。
この例のコードは次のようになります。
var lookup = MethodHandles.lookup();
var handle = MethodHandles
.privateLookupIn(IWasDesignedPoorly.class, lookup)
.findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);
またLookup
、VarHandle
オブジェクトをstatic final
フィールドとして使用することをお勧めします。
リフレクションに関する追加の注意:いくつかの特殊なケースで、同じ名前の複数のクラスが異なるパッケージに存在する場合、一番上の回答で使用されているリフレクションがオブジェクトから正しいクラスを選択できない可能性があることを確認しました。したがって、オブジェクトの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);
(これは私のために機能していなかったサンプルクラスです)
Manifoldの @JailBreakを使用して、タイプセーフなJavaリフレクションを直接実行できます。
@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は、内部で効率的なリフレクションコードを生成します。
マニホールドの詳細をご覧ください。
XrayInterfaceツールを使用すると非常に簡単です。不足しているゲッター/セッターを定義するだけです。
interface BetterDesigned {
Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}
設計が不十分なプロジェクトをX線撮影します。
IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());
内部的には、これは反射に依存しています。
ケースの問題を回避してみてください。データを設定/取得するカラスは、独自のクラスの1つです。
public setter(Field f, Object value)
とを作成するだけpublic Object getter(Field f)
です。これらのメンバー関数内で、独自にセキュリティチェックを行うこともできます。セッターの例:
class myClassName {
private String aString;
public set(Field field, Object value) {
// (A) do some checkings here for security
// (B) set the value
field.set(this, value);
}
}
もちろん、フィールド値を設定する前にjava.lang.reflect.Field
、を確認する必要があります。sString
私はこの手法を一般的なResultSet-to-and-from-model-mapperで使用します。