Jak odczytać wartość prywatnego pola z innej klasy w Javie?

482

Mam źle zaprojektowaną klasę w JARfirmie zewnętrznej i muszę uzyskać dostęp do jednego z jej prywatnych pól. Na przykład, dlaczego powinienem wybrać pole prywatne, czy jest to konieczne?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Jak mogę użyć odbicia, aby uzyskać wartość stuffIWant?

Frank Krueger
źródło

Odpowiedzi:

666

Aby uzyskać dostęp do pól prywatnych, musisz je pobrać z zadeklarowanych pól klasy, a następnie udostępnić je:

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

EDYCJA : jak skomentowali aperkins , zarówno dostęp do pola, ustawienie go jako dostępnego, jak i odzyskanie wartości może wyrzucić Exceptions, chociaż jedyne zaznaczone wyjątki, o których należy pamiętać, zostały skomentowane powyżej.

Zostanie NoSuchFieldExceptionrzucony, jeśli poprosisz o pole o nazwie, która nie odpowiada zadeklarowanemu polu.

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

Zostanie IllegalAccessExceptionwyrzucony, jeśli pole nie będzie dostępne (na przykład, jeśli jest prywatne i nie zostało udostępnione przez brak f.setAccessible(true)linii).

Do RuntimeExceptions, które mogą być rzucane są znakami SecurityExceptions (jeśli JVM użytkownika SecurityManagernie pozwalają na zmianę dostępności w polu), lub IllegalArgumentExceptions, jeśli spróbujesz i dostęp boiska na obiekcie nie typu dziedzinie swojej klasie za:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
źródło
4
czy możesz wyjaśnić komentarze do wyjątku? kod będzie działał, ale zgłasza wyjątki? czy kod może zgłaszać wyjątki?
Nir Levy,
1
@Nir - Nie - najprawdopodobniej kod będzie działał dobrze (ponieważ domyślny SecurityManager pozwoli na zmianę dostępności pól) - ale musisz obsłużyć sprawdzone wyjątki (złap je lub zadeklaruj, że zostaną ponownie wprowadzone ). Trochę zmodyfikowałem swoją odpowiedź. Może warto napisać mały test, aby się pobawić i zobaczyć, co się stanie
oxbow_lakes
3
Przepraszamy, ta odpowiedź jest dla mnie myląca. Być może pokażemy przykład typowej obsługi wyjątków. Wygląda na to, że wystąpiłyby wyjątki, gdy klasy były połączone niepoprawnie. Próbka kodu sprawia, że ​​wygląda na to, że wyjątki zostaną rzucone na odpowiednie linie.
LaFayette,
2
getDeclaredField()nie znajduje pól, jeśli są zdefiniowane w klasach nadrzędnych - musisz także iterować w górę przez hierarchię klas nadrzędnych, wzywając getDeclaredField()każde z nich, aż znajdziesz dopasowanie (po którym możesz zadzwonić setAccessible(true)) lub dojdziesz Object.
Luke Hutchison
1
@legend możesz zainstalować menedżera bezpieczeństwa, aby zabronić takiego dostępu. Od wersji Java 9 taki dostęp nie powinien działać ponad granicami modułów (chyba że moduł jest jawnie otwarty), chociaż istnieje faza przejściowa dotycząca egzekwowania nowych reguł. Poza tym wymaganie dodatkowego wysiłku, takiego jak Odbicie, zawsze miało znaczenie w przypadku privatepól innych niż pola. Zapobiega przypadkowemu dostępowi.
Holger,
159

Spróbuj FieldUtilsz apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
źródło
76
Jestem przekonany, że możesz rozwiązać większość problemów na świecie, łącząc kilka metod z commons-lang3.
Cameron,
@yegor, dlaczego java na to pozwala? oznacza, dlaczego Java pozwala na dostęp do prywatnych członków?
Asif Mushtaq
1
@ yegor256 Nadal mogę uzyskać dostęp do prywatnych członków C # i C ++ .. !! Następnie? wszyscy twórcy języków są słabi?
Asif Mushtaq
3
@UnKnown java nie. Istnieją dwie różne rzeczy - język java i java jako wirtualna maszyna java. Ten ostatni działa na kodzie bajtowym. Istnieją biblioteki do manipulowania tym. Tak więc język Java nie pozwala na używanie prywatnych pól poza zakresem lub nie pozwala na mutowanie ostatecznych odniesień. Ale można to zrobić za pomocą tego narzędzia. Co akurat znajduje się w standardowej bibliotece.
evgenii
3
@UnKnown jednym z powodów jest to, że Java nie ma dostępu „przyjaciela”, aby umożliwić dostęp do pola tylko za pomocą infrastruktury infrastruktury, takiej jak DI, ORM lub serializator XML / JSON. Takie struktury wymagają dostępu do pól obiektowych, aby poprawnie serializować lub inicjalizować wewnętrzny stan obiektu, ale nadal może być potrzebne odpowiednie wymuszenie enkapsulacji w czasie kompilacji dla logiki biznesowej.
Ivan Gammel,
26

Refleksja to nie jedyny sposób na rozwiązanie problemu (dostęp do prywatnej funkcjonalności / zachowania klasy / komponentu)

Alternatywnym rozwiązaniem jest wyodrębnienie klasy z pliku .jar, dekompilacja jej za pomocą (powiedzmy) Jode lub Jad , zmiana pola (lub dodanie akcesorium) i ponowne skompilowanie jej z oryginalnym plikiem .jar. Następnie umieść nową .class przed ścieżką .jarklasy lub ponownie włóż ją do .jar. (narzędzie jar umożliwia wyodrębnienie i ponowne włożenie do istniejącego pliku .jar)

Jak zauważono poniżej, rozwiązuje to szerszy problem dostępu / zmiany stanu prywatnego, a nie tylko dostępu / zmiany pola.

Oczywiście wymaga .jarto podpisania.

Brian Agnew
źródło
36
Ten sposób będzie dość bolesny dla zwykłego pola.
Valentin Rocher
1
Nie zgadzam się. Pozwala nie tylko uzyskać dostęp do twojego pola, ale także zmienić klasę, jeśli to konieczne, jeśli dostęp do pola okaże się niewystarczający.
Brian Agnew
4
Musisz to zrobić jeszcze raz. Co się stanie, jeśli słoik otrzyma aktualizację i użyjesz odbicia dla pola, które już nie istnieje? To dokładnie ten sam problem. Musisz po prostu sobie z tym poradzić.
Brian Agnew
4
Jestem zdumiony, jak bardzo ten głos jest oceniany, biorąc pod uwagę: a) wyróżniono go jako praktyczną alternatywę b) przeznaczony jest do scenariuszy, w których zmiana widoczności pola nie jest wystarczająca
Brian Agnew
2
@BrianAgnew może to tylko semantyczne, ale jeśli trzymamy się pytania (używając refleksji do odczytania prywatnego pola), nieużywanie refleksji jest od razu wewnętrzną sprzecznością. Ale zgadzam się, że zapewniasz dostęp do pola ... ale to pole nie jest już prywatne, więc ponownie nie trzymamy się pytania „przeczytaj pole prywatne”. Z drugiej strony, modyfikacja .jar może nie działać w niektórych przypadkach (podpisany jar), należy to robić za każdym razem, gdy jar jest aktualizowany, wymaga starannej manipulacji ścieżką klasy (której nie możesz w pełni kontrolować, jeśli jesteś wykonywany w pojemnik z aplikacją) itp.
Remi Morin
18

Jeszcze jedna opcja, o której jeszcze nie wspomniano: użyj Groovy . Groovy pozwala na dostęp do prywatnych zmiennych instancji jako efekt uboczny projektu języka. Niezależnie od tego, czy masz getter dla tego pola, możesz po prostu użyć

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
Lucas
źródło
4
OP specjalnie poprosił o Javę
Jochen
3
Wiele projektów Java ma dziś świetny charakter. Wystarczy, że projekt używa Groovy DSL Springa i na przykład będzie miał groovy na ścieżce klas. W takim przypadku ta odpowiedź jest przydatna i chociaż nie odpowiada bezpośrednio na PO, będzie korzystna dla wielu odwiedzających.
Amir Abiri
10

Za pomocą Reflection in Java możesz uzyskać dostęp do wszystkich private/publicpól i metod jednej klasy do drugiej. Ale zgodnie z dokumentacją Oracle w wadach sekcji zalecili:

„Ponieważ odbicie umożliwia kodowi wykonywanie operacji, które byłyby nielegalne w przypadku kodu nieodblaskowego, takich jak dostęp do prywatnych pól i metod, użycie odbicia może spowodować nieoczekiwane skutki uboczne, które mogą sprawić, że kod będzie niefunkcjonalny i może zniszczyć przenośność. Kod odblaskowy niszczy abstrakcje i dlatego może zmieniać zachowanie wraz z aktualizacjami platformy „

tutaj są następujące przyciągania kodu w celu zademonstrowania podstawowych koncepcji Reflection

Refleksja 1.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()));
        }
    }

}

Mam nadzieję, że to pomoże.

Simmant
źródło
5

Jak wspomina oxbow_lakes, możesz użyć refleksji, aby obejść ograniczenia dostępu (zakładając, że Twój SecurityManager na to pozwoli).

To powiedziawszy, jeśli ta klasa jest tak źle zaprojektowana, że ​​sprawia, że ​​uciekasz się do takiego hakowania, być może powinieneś poszukać alternatywy. Pewnie, że ten mały hack może cię zaoszczędzić kilka godzin, ale ile to będzie kosztowało cię w dalszej drodze?

Laurence Gonsalves
źródło
3
Mam więcej szczęścia, po prostu używam tego kodu do wyodrębnienia niektórych danych, po czym mogę wrzucić je z powrotem do Kosza.
Frank Krueger,
2
W takim razie odsuń się. :-)
Laurence Gonsalves
3
To nie daje odpowiedzi na pytanie. Aby skrytykować lub poprosić autora o wyjaśnienia, zostaw komentarz pod postem.
Mureinik,
@Mureinik - Odpowiada na pytanie słowami „możesz użyć refleksji”. Brakuje przykładu lub jakiegokolwiek większego wyjaśnienia lub sposobu, ale jest to odpowiedź. Głosuj, jeśli ci się nie podoba.
ArtOfWarfare
4

Skorzystaj ze środowiska Soot Java Optimization, aby bezpośrednio zmodyfikować kod bajtowy. http://www.sable.mcgill.ca/soot/

Soot jest całkowicie napisany w Javie i działa z nowymi wersjami Java.

pcpratts
źródło
3

Musisz wykonać następujące czynności:

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
źródło
2

Jeśli używasz Springa, ReflectionTestUtils zapewnia kilka przydatnych narzędzi, które pomagają tutaj przy minimalnym wysiłku. Jest opisany jako „do użytku w scenariuszach testów jednostkowych i integracyjnych” . Istnieje również podobna klasa o nazwie ReflectionUtils, ale jest ona opisana jako „przeznaczona wyłącznie do użytku wewnętrznego” - zobacz tę odpowiedź, aby zapoznać się z interpretacją tego, co to oznacza.

Aby rozwiązać opublikowany przykład:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
źródło
1
Jeśli zdecydujesz się użyć klasy Utils do Springa, naprawdę powinieneś użyć klasy nietestowej ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/... ), chyba że oczywiście faktycznie używam go do testów jednostkowych.
Synchronizacja
Możliwe, że ta klasa jest opisana jako „przeznaczona wyłącznie do użytku wewnętrznego”. Dodałem kilka informacji do odpowiedzi na ten temat. (Zachowałem przykład, ReflectionTestUtilsponieważ w moim doświadczeniu potrzebowałem tego rodzaju rzeczy tylko w sytuacji testowej).
Steve Chambers
1

Tylko dodatkowa uwaga na temat odbicia: zaobserwowałem w niektórych szczególnych przypadkach, gdy kilka klas o tej samej nazwie istnieje w różnych pakietach, to odbicie zastosowane w górnej odpowiedzi może nie wybrać właściwej klasy z obiektu. Jeśli więc wiesz, co to jest pakiet.klasa obiektu, lepiej uzyskać dostęp do jego wartości pola prywatnego w następujący sposób:

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

(To jest przykładowa klasa, która nie działała dla mnie)

xtof54
źródło
1

Możesz użyć @JailBreak Manifold do bezpośredniego, bezpiecznego dla Java odbicie typu:

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

public class Foo {
    private String stuffIWant;
}

@JailBreakodblokowuje foozmienną lokalną w kompilatorze dla bezpośredniego dostępu do wszystkich członków w Foohierarchii.

Podobnie możesz użyć metody rozszerzenia jailbreak () do jednorazowego użytku:

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

Za pomocą tej jailbreak()metody można uzyskać dostęp do dowolnego członka w Foohierarchii.

W obu przypadkach kompilator rozwiązuje dostęp do pola w sposób bezpieczny, tak jak w polu publicznym, podczas gdy kolektor generuje dla ciebie skuteczny kod odbicia pod maską.

Dowiedz się więcej o kolektorze .

Scott
źródło
1

Narzędzie XrayInterface jest dość łatwe . Po prostu zdefiniuj brakujące elementy pobierające / ustawiające, np

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

i xray swój źle zaprojektowany projekt:

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

Wewnętrznie polega to na refleksji.

Korona
źródło
0

Spróbuj obejść problem w przypadku, którego calass, który chcesz ustawić / uzyskać dane, jest jedną z twoich własnych klas.

Wystarczy utworzyć public setter(Field f, Object value)i public Object getter(Field f)do tego. Możesz nawet samodzielnie przeprowadzić kontrolę bezpieczeństwa wewnątrz tych funkcji członkowskich. Np. Dla setera:

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

Oczywiście, teraz musisz dowiedzieć się java.lang.reflect.Fieldo sStringprzed ustawieniem wartości pola.

Używam tej techniki w ogólnym narzędziu mapującym ResultSet-to-and-from-model-mapper.

karldegen
źródło