Як прочитати значення приватного поля з іншого класу на Java?


482

У мене погано розроблений клас у сторонній JARі мені потрібно отримати доступ до одного з його приватних полів. Наприклад, чому мені потрібно вибрати приватне поле, чи це потрібно?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Як я можу використовувати рефлексію, щоб отримати значення stuffIWant?

Відповіді:


666

Для доступу до приватних полів вам потрібно отримати їх із оголошених полів класу, а потім зробити доступними:

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

EDIT : як це зауважив aperkins , і доступ до поля, і встановлення його як доступного, і отримання значень можуть кидати Exceptions, хоча вище лише прокоментуються єдині перевірені винятки, про які потрібно пам’ятати.

NoSuchFieldExceptionБуде викинуте , якщо ви попросили поле імені , яке не відповідає заявленій галузі.

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

IllegalAccessExceptionБуде викинуте , якщо поле не було доступно (наприклад, якщо він є приватним і не доступний через пропускає в f.setAccessible(true)лінію.

В RuntimeExceptions , які можуть бути викинуті або є SecurityExceptions (якщо JVM - й SecurityManagerне дозволить вам змінити доступність польовий в), або IllegalArgumentExceptions, якщо ви спробуєте і доступ до поля на об'єкті не типу класу родовища в:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

4
чи можете ви поясніть коментарі щодо виключення? код запуститься, але викине виключення? або код може кинути винятки?
Нір Леві

1
@Nir - Ні -, швидше за все, код буде працювати нормально (оскільки за замовчуванням SecurityManager дозволить змінити доступність полів) - але вам доведеться обробляти перевірені винятки (або виловити їх, або оголосити їх повторними ). Я трохи змінив свою відповідь. Вам може бути добре написати невеликий тестовий випадок, щоб пограти і подивитися, що станеться
oxbow_lakes

3
Вибачте, ця відповідь мене бентежить. Можливо, покажіть приклад типової обробки виключень. Схоже, винятки трапляються, коли класи з'єднані неправильно. Зразок коду здається, що винятки будуть викинуті на відповідні llnes.
LaFayette

2
getDeclaredField()не знаходить поля, якщо вони визначені в батьківських класах - вам також потрібно повторити ієрархію батьківського класу, закликаючи getDeclaredField()кожне, доки не знайдете відповідність (після якої ви можете зателефонувати setAccessible(true)), або не досягнете Object.
Люк Хатчісон

1
@legend ви можете встановити менеджер безпеки, щоб заборонити такий доступ. Оскільки Java 9, такий доступ не повинен працювати через межі модуля (якщо модуль не відкрито явно), хоча існує перехідний етап щодо виконання нових правил. Крім того, вимагаючи додаткових зусиль, таких як Reflection, завжди змінювались privateполя, які не полягають. Це виключає випадковий доступ.
Холгер

159

Спробуйте FieldUtilsз apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

76
Я переконаний, що ви могли вирішити більшість світових проблем, з'єднавши кілька методів з commons-lang3.
Камерон

@yegor, чому Java дозволяє це? означає, чому Java дозволяє отримати доступ до приватних членів?
Асиф Муштак

1
@ yegor256 Я все ще можу отримати доступ до приватних членів C # і C ++ .. !! Тоді? всі мови творці слабкі?
Асиф Муштак

3
@UnK known java цього не робить. Є дві різні речі - мова java та java як віртуальна машина java. Останній працює на байт-коді. І є бібліотеки, які маніпулюють цим. Отже, мова java не дозволяє використовувати приватні поля поза межами області чи не дозволяє мутувати остаточні посилання. Але це можливо зробити за допомогою інструменту. Що просто трапляється всередині стандартної бібліотеки.
evgenii

3
@ Unn Відома одна з причин - Java не має доступу "до друзів", щоб дозволити доступ до поля лише за допомогою такої інфраструктури, як DI, ORM або серіалізатор XML / JSON. Такі рамки потребують доступу до об'єктних полів, щоб правильно серіалізувати або ініціалізувати внутрішній стан об'єкта, але вам все одно може знадобитися належне виконання компіляції в часі компіляції для вашої бізнес-логіки.
Іван Гаммель

26

Роздум - це не єдиний спосіб вирішити свою проблему (це доступ до приватної функціональності / поведінки класу / компонента)

Альтернативне рішення - витягнути клас з .jar, декомпілювати його за допомогою (скажімо) Jode або Jad , змінити поле (або додати аксесуар) та перекомпілювати його з оригінальним .jar. Потім поставте новий .class попереду в класний шлях .jarабо вставте його знову в .jar. (Утиліта jar дозволяє витягнути і вставити на існуючий .jar)

Як зазначалося нижче, це вирішує більш широку проблему доступу / зміни приватного стану, а не просто доступу / зміни поля.

Це вимагає .jar, звичайно, не підписуватися.


36
Цей спосіб буде досить болючим для простого поля.
Валентин Рочер

1
Я не погоджуюсь. Це дозволяє вам не тільки отримати доступ до свого поля, але і змінити клас, якщо це необхідно, якщо доступ до поля виявиться недостатнім.
Брайан Агнеу

4
Тоді вам доведеться це зробити ще раз. Що станеться, якщо jar отримує оновлення, і ви використовуєте відображення для поля, яке більше не існує? Це саме те саме питання. Ви просто повинні ним керувати.
Брайан Агнеу

4
Я здивований тому, наскільки це стає недооціненим, враховуючи, що а) це виділяється як практична альтернатива; б) він використовує сценарії, в яких зміни видимості поля недостатньо
Брайан Агнеу

2
@BrianAgnew, можливо, це просто семантично, але якщо ми дотримуємося питання (використовуючи рефлексію для читання приватного поля), не використовуючи рефлексію, це відразу суперечність. Але я згоден, ви надаєте доступ до поля ... але все-таки це поле більше не приватне, тому ми знову не дотримуємось питання "прочитати приватне поле". З іншого боку, модифікація .jar у певному випадку може не працювати (підписана jar), її потрібно робити щоразу, коли jar оновлюється, вимагати ретельного маніпулювання класним шляхом (яким ви не можете повністю керувати, якщо ви виконані в контейнер для додатків) тощо
Ремі Морін

18

Ще один варіант, про який ще не було сказано: використання Groovy . Groovy дозволяє отримати доступ до змінних приватних екземплярів як побічний ефект дизайну мови. Незалежно від того, чи є у вас геттер для поля, ви можете просто використовувати

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
ОП спеціально просила Яву
Йохен

3
У багатьох Java-проектах на сьогоднішній день входить багато. Досить, що проект використовує бурхливий DSL Spring, і вони, наприклад, матимуть groovy на classpath. У такому випадку ця відповідь є корисною, і хоча вона не відповідає безпосередньо ОП, вона буде корисною для багатьох відвідувачів.
Амір Абірі

10

За допомогою Reflection в Java ви можете отримати доступ до всіх private/publicполів і методів одного класу до іншого. Але відповідно до документації Oracle у розділі недоліки вони рекомендували:

"Оскільки відображення дозволяє коду виконувати операції, які були б незаконні в невідбиваючому коді, такі як доступ до приватних полів і методів, використання відображення може призвести до несподіваних побічних ефектів, що може зробити код нефункціональним і може знищити портативність. порушує абстракції і тому може змінити поведінку з оновленнями платформи "

ось наступні фрагменти коду, щоб продемонструвати основні поняття відбиття

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

}

Сподіваюся, це допоможе.


5

Як згадується oxbow_lakes, ви можете використовувати роздуми, щоб подолати обмеження доступу (якщо припустимо, що ваш SecurityManager дозволить вам).

Однак, якщо цей клас настільки погано розроблений, що змушує вас вдаватися до подібних хакерств, можливо, вам слід шукати альтернативу. Впевнений, що цей маленький хакер може врятувати вас кілька годин, але скільки це буде коштувати вам в дорозі?


3
Я щасливіший, ніж це насправді, я просто використовую цей код, щоб витягти деякі дані, після чого я можу перекинути його назад у кошик.
Френк Крюгер

2
Ну в такому випадку, рубіть геть. :-)
Лоранс Гонсалвес

3
Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх дописом.
Мурейник

@Mureinik - Він відповідає на запитання словами "Ви можете використовувати рефлексію". Не вистачає прикладу чи будь-якого більшого пояснення чи як, але це відповідь. Відмовляйтеся, якщо вам це не подобається.
ArtOfWarfare

4

Використовуйте рамку оптимізації Soot Java, щоб безпосередньо змінити байт-код. http://www.sable.mcgill.ca/soot/

Сажа повністю написана на Java і працює з новими версіями Java.


3

Вам потрібно зробити наступне:

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

2

Якщо використовується Spring, ReflectionTestUtils надає деякі зручні інструменти, які допомагають тут з мінімальними зусиллями. Він описаний як "для використання в сценаріях тестування блоків та інтеграції" . Існує також подібний клас під назвою ReflectionUtils, але він описаний як "Тільки призначений для внутрішнього використання" - див. Цю відповідь для тлумачення того, що це означає.

Для звернення до розміщеного прикладу:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
Якщо ви вирішили використовувати клас Utils до Spring, вам слід скористатись нетестовим ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) замість цього, якщо, звичайно, ви фактично використовує його для одиничних тестів.
Синхронізувати

Можливо, хоча цей клас описується як "Тільки призначений для внутрішнього використання". Я відповів на цю інформацію у відповідь. (Тримаю приклад, використовуючи, ReflectionTestUtilsяк на моєму досвіді, мені потрібно було коли-небудь робити подібні речі в тестовій ситуації.)
Стів Чемберс

1

Лише додаткове зауваження щодо рефлексії: я спостерігав у деяких особливих випадках, коли кілька класів з однаковою назвою існують у різних пакетах, що відображення, яке використовується у верхній відповіді, може не вдатися до вибору правильного класу з об'єкта. Тож якщо ви знаєте, що таке об'єкт 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);

(Це клас прикладу, який не працював для мене)


1

Ви можете використовувати КОЛЛЕКТОР в @JailBreak для прямого тіпобезопасного Java відображення:

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

public class Foo {
    private String stuffIWant;
}

@JailBreakрозблокує fooлокальну змінну в компіляторі для прямого доступу до всіх членів в Fooієрархії.

Аналогічно ви можете використовувати метод розширення jailbreak () для разового використання:

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

За допомогою jailbreak()методу ви можете отримати доступ до будь-якого члена в Fooієрархії Росії.

В обох випадках компілятор вирішує доступ до поля для вас безпечно, наче публічне поле, в той час як Manifold генерує для вас ефективний код відображення під кришкою.

Дізнайтеся більше про різноманітті .


1

З інструментом XrayInterface це досить просто . Просто визначте відсутні геттери / сетери, напр

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

і рентген вашого погано розробленого проекту:

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

Всередині це покладається на роздуми.


0

Спробуйте вирішити проблему для випадку, клас якого ви хочете встановити / отримати дані - це один із ваших власних класів.

Просто створіть 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.