Ich habe eine schlecht gestaltete Klasse in einer JAR
eines Drittanbieters und ich muss auf eines ihrer private -Felder zugreifen. Zum Beispiel Warum sollte ich ein privates Feld auswählen müssen?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
Wie kann ich Reflection verwenden, um den Wert von stuffIWant
zu erhalten?
Um auf private Felder zugreifen zu können, müssen Sie sie aus den deklarierten -Feldern der Klasse abrufen und dann zugänglich machen:
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
EDIT: Wie von aperkins kommentiert, werden beide, die auf das Feld zugreifen, es als zugänglich definieren und den Wert abrufen, alle Exception
s werfen, obwohl die einzigen checked -Ausnahmen, die Sie sein müssen achtsam sind oben kommentiert.
Die Variable NoSuchFieldException
würde ausgelöst, wenn Sie nach einem Feld mit einem Namen fragen, der nicht mit einem deklarierten Feld übereinstimmt.
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
Die Variable IllegalAccessException
würde ausgelöst, wenn das Feld nicht verfügbar war (z. B. wenn es privat ist und nicht über die Zeile f.setAccessible(true)
erreichbar gemacht wurde).
Die RuntimeException
s, die ausgelöst werden können, sind entweder SecurityException
s (wenn die JVM-Variable SecurityManager
Sie nicht zulässt, die Eingabehilfen eines Felds zu ändern) oder IllegalArgumentException
s, wenn Sie versuchen, auf das Feld eines Objekts zuzugreifen, das nicht dem Typ des Felds entspricht:
f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
Versuchen Sie FieldUtils
aus Apache commons-lang3:
FieldUtils.readField(object, fieldName, true);
Reflexion ist nicht die einzige Möglichkeit, Ihr Problem zu lösen (dh, auf die privaten Funktionen/das Verhalten einer Klasse/Komponente zuzugreifen).
Eine alternative Lösung besteht darin, die Klasse aus der .jar-Datei zu extrahieren, mit (say) Jode oder Jad zu dekompilieren, das Feld zu ändern (oder einen Accessor hinzuzufügen) und mit der ursprünglichen .jar-Datei erneut zu kompilieren. Fügen Sie dann die neue Klasse vor .jar
in den Klassenpfad ein oder fügen Sie sie erneut in .jar
ein. (Das jar-Dienstprogramm ermöglicht das Extrahieren und Wiedereinsetzen in eine vorhandene .jar-Datei.)
Wie weiter unten erwähnt, löst dies das umfassendere Problem des Zugriffs auf/Ändern des privaten Zustands und nicht nur des Zugriffs auf ein Feld.
Dazu muss natürlich der .jar
nicht signiert sein.
Eine andere Option, die noch nicht erwähnt wurde: use Groovy . Groovy ermöglicht den Zugriff auf private Instanzvariablen als Nebeneffekt des Sprachdesigns. Ob Sie einen Getter für das Feld haben oder nicht, können Sie einfach verwenden
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
Mit Hilfe von Reflection in Java können Sie auf alle private/public
-Felder und -Methoden einer Klasse auf eine andere zugreifen. Wie auch immer in der OracleDokumentation im Abschnitt backbacks wird empfohlen:
"Da Reflexion Code für das Durchführen von Operationen zulässt, die in nicht reflektierendem Code unzulässig sind, wie z. B. beim Zugriff auf private Felder und Methoden, kann die Verwendung von Reflektionen zu unerwarteten Nebeneffekten führen, die dazu führen, dass Code funktionsunfähig wird und die Portabilität zerstört Reflektierender Code unterbricht Abstraktionen und kann daher das Verhalten bei Upgrades der Plattform ändern "
hier sind die folgenden Code-Ausschnitte, um grundlegende Konzepte von Reflection zu demonstrieren.
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()));
}
}
}
Ich hoffe es wird helfen.
Wie von oxbow_lakes erwähnt, können Sie Reflection verwenden, um die Zugriffsbeschränkungen zu umgehen (vorausgesetzt, Ihr SecurityManager wird Sie zulassen).
Wenn diese Klasse jedoch so schlecht gestaltet ist, dass Sie dazu gezwungen werden, zu so viel Hacker zu greifen, sollten Sie vielleicht nach einer Alternative suchen. Sicher, dieser kleine Hacker spart Ihnen jetzt ein paar Stunden, aber wie viel kostet es Sie die Straße hinunter?
Verwenden Sie das Soot Java Optimization-Framework, um den Bytecode direkt zu ändern http://www.sable.mcgill.ca/soot/
Ruß ist vollständig in Java geschrieben und arbeitet mit neuen Java-Versionen.
Sie müssen Folgendes tun:
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);
}
Wenn Sie Spring verwenden, bietet ReflectionTestUtils einige nützliche Tools, die hier mit minimalem Aufwand helfen. Es wird als "zur Verwendung in Unit- und Integrationstestszenarien" beschrieben. Es gibt auch eine ähnliche Klasse mit dem Namen ReflectionUtils , die jedoch als "Nur für den internen Gebrauch" beschrieben wird - siehe diese Antwort für eine Interpretation dessen, was dies bedeutet.
So adressieren Sie das veröffentlichte Beispiel:
Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Noch ein Hinweis zur Reflektion: Ich habe in einigen speziellen Fällen festgestellt, dass mehrere Reflektoren mit demselben Namen in verschiedenen Paketen vorhanden sind. Diese Reflektion, wie sie in der obersten Antwort verwendet wird, kann möglicherweise nicht die richtige Klasse aus dem Objekt auswählen. Wenn Sie also wissen, was die package.class des Objekts ist, ist es besser, auf die privaten Feldwerte wie folgt zuzugreifen:
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);
(Dies ist die Beispielklasse, die für mich nicht funktionierte.)
Sie können Manifolds@JailBreak für eine direkte, typsichere Java-Reflektion verwenden:
@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;
public class Foo {
private String stuffIWant;
}
@JailBreak
gibt die lokale Variable foo
im Compiler für den direkten Zugriff auf alle Member in der Hierarchie von Foo
frei.
Ebenso können Sie die Erweiterungsmethode jailbreak () zur einmaligen Verwendung verwenden:
foo.jailbreak().stuffIWant = "123";
Über die jailbreak()
-Methode können Sie auf jedes Mitglied in der Hierarchie von Foo
zugreifen.
In beiden Fällen löst der Compiler den Feldzugriff für Sie typsicher auf, als ob ein öffentliches Feld, während Manifold unter der Haube einen effizienten Reflexionscode für Sie generiert.
Erfahren Sie mehr über Manifold .