Wie lese ich den Wert eines privaten Feldes aus einer anderen Klasse in Java?

482

Ich habe eine schlecht gestaltete Klasse in einem Drittanbieter JARund muss auf eines seiner privaten Felder zugreifen . Warum sollte ich beispielsweise ein privates Feld auswählen müssen?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Wie kann ich Reflexion verwenden, um den Wert von zu erhalten stuffIWant?

Frank Krueger
quelle

Antworten:

668

Um auf private Felder zugreifen zu können, müssen Sie sie aus den deklarierten Feldern der Klasse abrufen und anschließend zugänglich machen:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

BEARBEITEN : Wie von Aperkins kommentiert , kann sowohl der Zugriff auf das Feld als auch das Festlegen des Zugriffs als auch das Abrufen des Werts Exceptions auslösen , obwohl die einzigen geprüften Ausnahmen, die Sie beachten müssen , oben kommentiert sind.

Das NoSuchFieldExceptionwird ausgelöst, wenn Sie nach einem Feld mit einem Namen fragen, der keinem deklarierten Feld entspricht.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

Das IllegalAccessExceptionwürde ausgelöst, wenn das Feld nicht zugänglich wäre (zum Beispiel, wenn es privat ist und nicht zugänglich gemacht wurde, indem die f.setAccessible(true)Zeile verpasst wurde .

Die RuntimeExceptions, die ausgelöst werden können, sind entweder SecurityExceptions (wenn die JVMs SecurityManageres Ihnen nicht erlauben, die Zugänglichkeit eines Felds zu ändern) oder IllegalArgumentExceptions, wenn Sie versuchen, auf das Feld eines Objekts zuzugreifen, das nicht vom Klassentyp des Felds ist:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
quelle
4
Können Sie bitte die Ausnahmekommentare erläutern? Der Code wird ausgeführt, aber Ausnahmen auslösen? oder kann der Code Ausnahmen auslösen?
Nir Levy
1
@Nir - Nein - aller Wahrscheinlichkeit nach wird der Code nur gut laufen (als Standard - Security werden Felder accesibility erlauben geändert werden) - aber man muss Griff Ausnahmen geprüft (entweder sie fangen oder zu erklären sein , erneut ausgelöst ). Ich habe meine Antwort ein wenig geändert. Es könnte gut für Sie sein, einen kleinen Testfall zu schreiben, um
herumzuspielen
3
Entschuldigung, diese Antwort ist für mich verwirrend. Zeigen Sie möglicherweise ein Beispiel für die typische Ausnahmebehandlung. Es scheint, als würden die Ausnahmen auftreten, wenn die Klassen falsch miteinander verbunden sind. Das Codebeispiel lässt den Eindruck entstehen, dass die Ausnahmen bei den entsprechenden llnes ausgelöst werden.
LaFayette
2
getDeclaredField()findet keine Felder, wenn sie in übergeordneten Klassen definiert sind - Sie müssen auch die übergeordnete Klassenhierarchie durchlaufen und getDeclaredField()jedes aufrufen , bis Sie eine Übereinstimmung finden (nach der Sie aufrufen können setAccessible(true)) oder erreichen Object.
Luke Hutchison
1
@legend Sie können einen Sicherheitsmanager installieren, um einen solchen Zugriff zu verbieten. Seit Java 9 soll ein solcher Zugriff nicht über Modulgrenzen hinweg funktionieren (es sei denn, ein Modul ist explizit geöffnet), obwohl es eine Übergangsphase hinsichtlich der Durchsetzung der neuen Regeln gibt. Darüber hinaus machte das Erfordernis zusätzlicher Anstrengungen wie Reflexion für Nichtfelder immer einen Unterschied private. Dies schließt einen versehentlichen Zugriff aus.
Holger
159

Versuchen Sie es mit FieldUtilsapache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
quelle
76
Ich bin überzeugt, dass Sie die meisten Probleme der Welt lösen können, indem Sie einige Methoden aus commons-lang3 aneinanderreihen.
Cameron
@yegor warum Java dies zulassen? meine, warum Java erlaubt, auf private Mitglieder zuzugreifen?
Asif Mushtaq
1
@ yegor256 Ich kann auch weiterhin auf private C # - und C ++ - Mitglieder zugreifen .. !! Dann? Alle Sprachschöpfer sind schwach?
Asif Mushtaq
3
@UnKnown Java nicht. Es gibt zwei verschiedene Dinge - Java-Sprache und Java als Java Virtual Machine. Letzterer arbeitet mit Bytecode. Und es gibt Bibliotheken, die das manipulieren können. In der Java-Sprache können Sie also keine privaten Felder außerhalb des Gültigkeitsbereichs verwenden oder endgültige Verweise nicht mutieren. Dies ist jedoch mit dem Tool möglich. Was sich zufällig in der Standardbibliothek befindet.
evgenii
3
@UnKnown Einer der Gründe ist, dass Java keinen "Freund" -Zugriff hat, um den Zugriff auf das Feld nur über ein Infrastruktur-Framework wie DI, ORM oder XML / JSON-Serializer zu ermöglichen. Solche Frameworks benötigen Zugriff auf Objektfelder, um den internen Status eines Objekts korrekt zu serialisieren oder zu initialisieren. Möglicherweise benötigen Sie jedoch weiterhin eine ordnungsgemäße Durchsetzung der Kapselung zur Kompilierungszeit für Ihre Geschäftslogik.
Ivan Gammel
26

Reflexion ist nicht die einzige Möglichkeit, Ihr Problem zu lösen (dh auf die private Funktionalität / das Verhalten einer Klasse / Komponente zuzugreifen).

Eine alternative Lösung besteht darin, die Klasse aus der JAR-Datei zu extrahieren, sie mit (z. B.) Jode oder Jad zu dekompilieren , das Feld zu ändern (oder einen Accessor hinzuzufügen) und sie mit der ursprünglichen JAR-Datei neu zu kompilieren. Stellen Sie dann die neue .class vor die .jarin den Klassenpfad oder fügen Sie sie erneut in die ein .jar. (Mit dem Dienstprogramm jar können Sie eine vorhandene .jar-Datei extrahieren und erneut einfügen.)

Wie unten erwähnt, löst dies das umfassendere Problem des Zugriffs auf / der Änderung des privaten Status, anstatt einfach auf ein Feld zuzugreifen / diesen zu ändern.

Dies setzt .jarnatürlich voraus, dass das nicht unterschrieben wird.

Brian Agnew
quelle
36
Dieser Weg wird nur für ein einfaches Feld ziemlich schmerzhaft sein.
Valentin Rocher
1
Ich stimme dir nicht zu. Sie können damit nicht nur auf Ihr Feld zugreifen, sondern bei Bedarf auch die Klasse ändern, wenn sich herausstellt, dass der Zugriff auf das Feld nicht ausreicht.
Brian Agnew
4
Dann musst du das nochmal machen. Was passiert, wenn das Glas aktualisiert wird und Sie Reflection für ein Feld verwenden, das nicht mehr vorhanden ist? Es ist genau das gleiche Problem. Sie müssen es nur verwalten.
Brian Agnew
4
Ich bin erstaunt darüber, wie sehr dies herabgestuft wird, wenn a) es als praktische Alternative hervorgehoben wird b) Szenarien berücksichtigt werden, in denen eine Änderung der Sichtbarkeit eines Feldes nicht ausreicht
Brian Agnew
2
@BrianAgnew Vielleicht ist es nur semantisch, aber wenn wir uns an die Frage halten (Reflektion zum Lesen eines privaten Feldes verwenden), ist Reflektion sofort ein Selbstwiderspruch. Aber ich bin damit einverstanden, dass Sie Zugriff auf das Feld gewähren ... aber das Feld ist immer noch nicht mehr privat, so dass wir uns wieder nicht an das "Lesen eines privaten Feldes" der Frage halten. Aus einem anderen Blickwinkel funktioniert die .jar-Modifikation in einigen Fällen möglicherweise nicht (signiertes JAR). Sie muss jedes Mal durchgeführt werden, wenn das JAR aktualisiert wird. Sie erfordert eine sorgfältige Bearbeitung des Klassenpfads (die Sie möglicherweise nicht vollständig steuern können, wenn Sie in einem ausgeführt werden Anwendungsbehälter) usw.
Remi Morin
18

Eine andere Option, die noch nicht erwähnt wurde: Verwenden Sie Groovy . Mit Groovy können Sie als Nebeneffekt beim Entwurf der Sprache auf private Instanzvariablen zugreifen. Unabhängig davon, ob Sie einen Getter für das Feld haben oder nicht, können Sie ihn einfach verwenden

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
lucas
quelle
4
Das OP fragte speziell nach Java
Jochen
3
Viele Java-Projekte enthalten heute Groovy. Es reicht aus, dass das Projekt Spring's grooviges DSL verwendet und sie zum Beispiel auf dem Klassenpfad groovig sind. In diesem Fall ist diese Antwort nützlich, und obwohl sie nicht direkt auf das OP antwortet, ist sie für viele Besucher von Vorteil.
Amir Abiri
10

Mit der Reflection in Java können Sie auf alle private/publicFelder und Methoden einer Klasse für eine andere zugreifen. Gemäß der Oracle- Dokumentation in dem Abschnitt Nachteile, die sie empfohlen haben:

"Da durch Reflexion Code Operationen ausführen kann, die in nicht reflektierendem Code illegal wären, z. B. der Zugriff auf private Felder und Methoden, kann die Verwendung von Reflexion zu unerwarteten Nebenwirkungen führen, die den Code funktionsunfähig machen und die Portabilität zerstören können. Reflektierender Code bricht Abstraktionen und kann daher das Verhalten bei Upgrades der Plattform ändern "

Im Folgenden finden Sie folgende Code-Schnappschüsse, um grundlegende Konzepte der Reflexion 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()));
        }
    }

}

Hoffe es wird helfen.

Simmant
quelle
5

Wie in oxbow_lakes erwähnt, können Sie mithilfe von Reflection die Zugriffsbeschränkungen umgehen (vorausgesetzt, Ihr SecurityManager lässt dies zu).

Das heißt, wenn diese Klasse so schlecht gestaltet ist, dass Sie auf solche Hackerei zurückgreifen, sollten Sie vielleicht nach einer Alternative suchen. Sicher, dieser kleine Hack spart Ihnen jetzt vielleicht ein paar Stunden, aber wie viel kostet es Sie später?

Laurence Gonsalves
quelle
3
Ich bin glücklicher als das, ich benutze diesen Code nur, um einige Daten zu extrahieren, danach kann ich sie zurück in den Papierkorb werfen.
Frank Krueger
2
Nun, in diesem Fall hacken Sie weg. :-)
Laurence Gonsalves
3
Dies gibt keine Antwort auf die Frage. Um einen Autor zu kritisieren oder um Klärung zu bitten, hinterlassen Sie einen Kommentar unter seinem Beitrag.
Mureinik
@ Mureinik - Es beantwortet die Frage mit den Worten "Sie können Reflexion verwenden". Es fehlt ein Beispiel oder eine größere Erklärung oder wie, aber es ist eine Antwort. Stimmen Sie ab, wenn Sie es nicht mögen.
ArtOfWarfare
4

Verwenden Sie das Soot Java Optimization Framework, um den Bytecode direkt zu ändern. http://www.sable.mcgill.ca/soot/

Soot ist vollständig in Java geschrieben und funktioniert mit neuen Java-Versionen.

pcpratts
quelle
3

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);
}
Luke Hutchison
quelle
2

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 aber wie beschrieben wird : „Nur für den internen Gebrauch bestimmt“ - siehe diese Antwort für eine Interpretation dessen , was das bedeutet.

So adressieren Sie das veröffentlichte Beispiel:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
quelle
1
Wenn Sie sich dazu entschließen, eine Utils-Klasse bis zum Frühjahr zu verwenden, sollten Sie stattdessen die Nicht-Test-Klasse ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) verwenden, es sei denn, Sie natürlich Ich benutze es tatsächlich für Unit-Tests.
Synchronisieren Sie den
Möglicherweise, obwohl diese Klasse als "Nur für den internen Gebrauch bestimmt" beschrieben wird. Ich habe der Antwort dazu einige Informationen hinzugefügt. (Habe das Beispiel ReflectionTestUtilsso beibehalten, wie ich es meiner Erfahrung nach nur in einer Testsituation tun musste.)
Steve Chambers
1

Nur ein zusätzlicher Hinweis zur Reflexion: Ich habe in einigen Sonderfällen festgestellt, dass bei mehreren Klassen mit demselben Namen in verschiedenen Paketen die in der oberen Antwort verwendete Reflexion möglicherweise nicht die richtige Klasse aus dem Objekt auswählt. Wenn Sie also wissen, was die package.class des Objekts ist, ist es besser, wie folgt auf seine privaten Feldwerte 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 funktioniert hat)

xtof54
quelle
1

Sie können Manifolds @JailBreak für die direkte, typsichere Java-Reflexion verwenden:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakSchaltet die foolokale Variable im Compiler für den direkten Zugriff auf alle Mitglieder in Fooder Hierarchie frei.

Ebenso können Sie die Erweiterungsmethode jailbreak () für die einmalige Verwendung verwenden:

foo.jailbreak().stuffIWant = "123";

Über die jailbreak()Methode können Sie auf jedes Mitglied in Fooder Hierarchie zugreifen .

In beiden Fällen löst der Compiler den Feldzugriff für Sie typsicher wie ein öffentliches Feld auf, während Manifold unter der Haube einen effizienten Reflektionscode für Sie generiert.

Erfahren Sie mehr über Manifold .

Scott
quelle
1

Mit dem Tool XrayInterface ist das ganz einfach . Definieren Sie einfach die fehlenden Getter / Setter, z

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

und röntgen Sie Ihr schlecht gestaltetes Projekt:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

Intern beruht dies auf Reflexion.

Corona
quelle
0

Versuchen Sie, das Problem für den Fall zu umgehen, dessen Kalass Sie Daten festlegen / abrufen möchten, ist eine Ihrer eigenen Klassen.

Erstellen Sie einfach ein public setter(Field f, Object value)und public Object getter(Field f)dafür. Innerhalb dieser Mitgliedsfunktionen können Sie sogar selbst eine Sicherheitsüberprüfung durchführen. ZB für den Setter:

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);
    }
}

Natürlich, jetzt müssen Sie das herausfinden, java.lang.reflect.Fieldfür sStringvor dem Einstellen von Feldwert.

Ich verwende diese Technik in einem generischen ResultSet-to-and-from-Model-Mapper.

karldegen
quelle