Вопрос по private, field, java, class, reflection – Как прочитать значение частного поля из другого класса в Java?

431

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

Ваш Ответ

11   ответов
4

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

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

16

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

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

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

124

ПытатьсяFieldUtils от apache commons-lang3:

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

Вам нужно сделать следующее:

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

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

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

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

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

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

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

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

Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом.
@Mureinik - ответ на вопрос со словами «вы можете использовать отражение». Ему не хватает примера или какого-либо большего объяснения или как, но это ответ. Проголосуйте, если вам это не нравится.
Ну, в таком случае, взломать. :-)
На самом деле мне повезло больше, я просто использую этот код для извлечения некоторых данных, после чего я могу выбросить их обратно в корзину. Frank Krueger
596

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

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

EDIT: как прокомментировалaperkinsи доступ к полю, и установление его как доступного, и получение значения приведут кExceptionс, хотя единственныйchecked исключения, о которых нужно помнить, комментируются выше.

NoSuchFieldException будет брошено, если вы запросите поле с именем, которое не соответствует объявленному полю.

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

IllegalAccessException будет выброшено, если поле было недоступно (например, если оно является закрытым и не было сделано доступным из-за пропускаf.setAccessible(true) линия.

RuntimeExceptions, которые могут быть брошены, либоSecurityExceptions (если JVM 'sSecurityManager не позволит вам изменить доступность поля) илиIllegalArgumentExceptions, если вы попытаетесь получить доступ к полю объекта, не относящегося к типу поля:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
Извините, этот ответ меня смущает. Возможно покажите пример типичной обработки исключений. Кажется, что исключения возникают, когда классы соединены неправильно. Пример кода создает впечатление, что исключения будут выброшены в соответствующие строки.
@Nir - Нет - по всей вероятности, код будет работать нормально (так как по умолчанию SecurityManager позволяет изменять доступ к полям), но вы должныhandle проверенные исключения (либо ловите их, либо объявляйте ихrethrown). Я немного изменил свой ответ. Может быть, вам стоит написать небольшой тестовый пример, чтобы поиграть и посмотреть, что произойдет
не могли бы вы объяснить комментарии об исключениях? код будет работать, но будет выбрасывать исключения? или код может генерировать исключения?
getDeclaredField() не находит поля, если они определены в родительских классах - вам также нужно пройтись по иерархии родительского класса, вызываяgetDeclaredField() на каждом, пока не найдете совпадение (после которого вы можете позвонитьsetAccessible(true)) или вы достигнетеObject.
@legend вы можете установить менеджер безопасности, чтобы запретить такой доступ. Начиная с Java 9, такой доступ не должен работать через границы модуля (если только модуль не открыт явно), хотя существует переходная фаза в отношении применения новых правил. Кроме того, необходимость дополнительных усилий, таких как Reflection, всегда отличалаprivate поля. Это исключает случайный доступ.
8

С использованиемReflection in Java Вы можете получить доступ ко всемprivate/public поля и методы одного класса в другой. Но согласноOracle документация  в разделеdrawbacks они рекомендовали:

"Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform"

Вот следующие фрагменты кода, чтобы продемонстрировать основные понятияReflection

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

}

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

0

Ты можешь использоватьКоллектор & APOS; s @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 генерирует эффективный код отражения для вас изнутри.

Узнайте больше оМногообразие.

0

Если использовать Spring,ReflectionTestUtils предоставляет некоторые удобные инструменты, которые помогут здесь с минимальными усилиями. Это описано как"for use in unit and integration testing scenarios", Существует также похожий класс с именемReflectionUtils но это описано как"Only intended for internal use" - увидетьэтот ответ для интерпретации того, что это значит.

Чтобы обратиться к опубликованному примеру:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Возможно, хотя этот класс описан как «Только для внутреннего использования». Я добавил некоторую информацию в ответ по этому поводу. (Сохранили пример используяReflectionTestUtils как и в моем опыте, мне только нужно было делать подобные вещи в тестовой ситуации.)
Если вы решили использовать класс Utils от Spring, вам действительно следует использовать не тестовый класс (docs.spring.io/spring-framework/docs/current/javadoc-api/org/…вместо этого, если, конечно, вы фактически не используете его для модульных тестов.

Похожие вопросы