Как прочитать значение частного поля из другого класса в Java?

482

У меня плохо спроектированный класс в третьей стороне, JARи мне нужно получить доступ к одному из его личных полей. Например, зачем мне нужно выбирать личное поле, это необходимо?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Как я могу использовать отражение, чтобы получить значение stuffIWant?

Фрэнк Крюгер
источник

Ответы:

668

Чтобы получить доступ к закрытым полям, вам нужно получить их из объявленных полей класса, а затем сделать их доступными:

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

РЕДАКТИРОВАТЬ : как было прокомментировано 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
oxbow_lakes
источник
4
не могли бы вы объяснить комментарии об исключениях? код будет работать, но будет выбрасывать исключения? или код может генерировать исключения?
Нир Леви
1
@Nir - Нет - по всей вероятности, код будет работать нормально (так как по умолчанию SecurityManager позволяет изменять доступность полей), но вы должны обрабатывать проверенные исключения (либо перехватывать их, либо объявлять их перебрасываемыми ). Я немного изменил свой ответ. Может быть, вам стоит написать небольшой тестовый пример, чтобы поиграть и посмотреть, что получится
oxbow_lakes
3
Извините, этот ответ меня смущает. Возможно покажите пример типичной обработки исключений. Кажется, что исключения возникают, когда классы соединены неправильно. Пример кода создает впечатление, что исключения будут выброшены в соответствующие строки.
LaFayette
2
getDeclaredField()не находит поля, если они определены в родительских классах - вы также должны пройти по иерархии родительского класса, вызывая getDeclaredField()каждое из них, пока не найдете совпадение (после которого вы можете вызвать setAccessible(true)) или пока не достигнете Object.
Люк Хатчисон
1
@legend вы можете установить менеджер безопасности, чтобы запретить такой доступ. Начиная с Java 9, такой доступ не должен работать через границы модуля (если модуль не открыт явно), хотя есть переходный этап в отношении применения новых правил. Кроме того, необходимость дополнительных усилий, таких как Reflection, всегда отличала неполя private. Это исключает случайный доступ.
Хольгер
159

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

FieldUtils.readField(object, fieldName, true);
yegor256
источник
76
Я убежден, что вы можете решить большинство мировых проблем, собрав несколько методов из commons-lang3.
Кэмерон
@yegor почему ява это позволяет? значит почему java разрешает доступ частным пользователям?
Асиф Муштак
1
@ yegor256 Я также могу получить доступ к закрытым членам C # и C ++ .. !! Затем? все языки создатели слабы?
Асиф Муштак
3
@ Unknown Java не имеет. Есть две разные вещи - язык Java и Java как виртуальная машина Java. Последний работает по байт-коду. И есть библиотеки, чтобы манипулировать этим. Таким образом, язык Java не позволяет вам использовать закрытые поля вне области видимости или не позволяет изменять окончательные ссылки. Но это можно сделать с помощью инструмента. Который просто случается внутри стандартной библиотеки.
Евгений
3
@UnKnown одной из причин является то, что Java не имеет «дружественного» доступа, чтобы разрешить доступ к полю только инфраструктурой инфраструктуры, такой как DI, ORM или сериализатор XML / JSON. Такие платформы нуждаются в доступе к полям объекта, чтобы правильно сериализовать или инициализировать внутреннее состояние объекта, но вам все еще может потребоваться правильное применение инкапсуляции во время компиляции для вашей бизнес-логики.
Иван Гаммель
26

Отражение - не единственный способ решить вашу проблему (то есть получить доступ к закрытой функциональности / поведению класса / компонента)

Альтернативное решение состоит в том, чтобы извлечь класс из .jar, декомпилировать его с помощью, скажем, Jode или Jad , изменить поле (или добавить метод доступа) и перекомпилировать его с исходным .jar. Затем поместите новый .class впереди .jarв classpath или заново вставьте его в.jar . (утилита jar позволяет вам извлечь и снова вставить в существующий .jar)

Как отмечено ниже, это решает более широкую проблему доступа / изменения частного состояния, а не просто доступа / изменения поля.

Это требует, чтобы .jarне быть подписанным, конечно.

Брайан Агнью
источник
36
Этот путь будет довольно болезненным для простого поля.
Валентин Роше
1
Я не согласен. Это позволяет вам не только получить доступ к вашему полю, но и при необходимости изменить класс, если доступ к полю оказывается недостаточным.
Брайан Агнью
4
Тогда ты должен сделать это снова. Что произойдет, если банка получит обновление и вы используете отражение для поля, которого больше не существует? Это точно такая же проблема. Вы просто должны справиться с этим.
Брайан Агнью
4
Я удивлен тем, насколько за это проголосовали, учитывая, что а) он выделен как практическая альтернатива б) он обслуживает сценарии, в которых изменение видимости поля недостаточно
Брайан Агнью,
2
@BrianAgnew, может быть, это просто семантика, но если мы придерживаемся вопроса (используя рефлексию для чтения приватного поля), то не использование рефлексии сразу становится внутренним противоречием. Но я согласен, что вы предоставляете доступ к полю ... но все же поле больше не является частным, поэтому мы опять не будем придерживаться вопроса "читать личное поле". С другой стороны, модификация .jar в некоторых случаях может не работать (подписанный jar), необходимо выполнять каждый раз, когда jar обновляется, требовать осторожного манипулирования classpath (которым вы не можете полностью управлять, если вы выполняетесь в контейнер приложения) и т. д.
Реми Морин
18

Еще один вариант, который еще не был упомянут: используйте Groovy . Groovy позволяет вам получить доступ к частным переменным экземпляра как побочный эффект дизайна языка. Есть ли у вас добытчик для поля, вы можете просто использовать

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
Лукас
источник
4
ОП специально попросил Java
Йохен
3
Многие проекты Java сегодня включают в себя Groovy. Достаточно того, что в проекте используется классный DSL Spring, и у них, например, будет классный путь. В этом случае этот ответ полезен, и хотя он не отвечает непосредственно на ФП, он будет полезен для многих посетителей.
Амир Абири
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()));
        }
    }

}

Надеюсь, это поможет.

Simmant
источник
5

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

Тем не менее, если этот класс настолько плохо спроектирован, что заставляет вас прибегать к таким хакерским атакам, возможно, вам следует искать альтернативу. Конечно, этот маленький хакер может сэкономить вам несколько часов, но сколько это будет стоить вам в будущем?

Лоуренс Гонсалвес
источник
3
На самом деле мне повезло больше, я просто использую этот код для извлечения некоторых данных, после чего я могу выбросить их обратно в корзину.
Фрэнк Крюгер
2
Ну, в таком случае, взломать. :-)
Лоуренс Гонсалвес
3
Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом.
Mureinik
@Mureinik - Он отвечает на вопрос со словами «вы можете использовать отражение». Ему не хватает примера или какого-либо большего объяснения или как, но это ответ. Проголосуй, если тебе не нравится.
ArtOfWarfare
4

Используйте платформу Soot Java Optimization для непосредственного изменения байт-кода. http://www.sable.mcgill.ca/soot/

Сажа полностью написана на Java и работает с новыми версиями Java.

pcpratts
источник
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);

(Это пример класса, который не работал для меня)

xtof54
источник
1

Вы можете использовать @JailBreak от Manifold для прямого, типобезопасного отражения 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иерархии «S.

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

Узнайте больше о коллектор .

Скотт
источник
1

Это довольно легко с инструментом XrayInterface . Просто определите отсутствующие методы получения / установки, например

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

и xray ваш плохо разработанный проект:

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.Fieldfor sStringдо установки значения поля.

Я использую эту технику в универсальном сопоставителе ResultSet-to-and-from-model-mapper.

karldegen
источник