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

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

15 ответов

Решение

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

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

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

FieldUtils.readField(object, fieldName, true);

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

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

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

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

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

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

Используя 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()));
        }
    }

}

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

Java 9 представила дескрипторы переменных . Вы можете получить доступ к частному полю класса, используя их.

Код для вашего примера будет выглядеть следующим образом:

      var lookup = MethodHandles.lookup();
var handle = MethodHandles
    .privateLookupIn(IWasDesignedPoorly.class, lookup)
    .findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);

Целесообразно также использовать Lookupа также VarHandleобъекты как static finalполя.

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

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

При использовании Spring ReflectionTestUtils предоставляет несколько удобных инструментов, которые помогут здесь с минимальными усилиями. Он описывается как "предназначенный для использования в сценариях модульного и интеграционного тестирования". Существует также аналогичный класс с именем ReflectionUtils, но он описан как "Предназначен только для внутреннего использования" - см. Этот ответ для интерпретации того, что это значит.

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

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

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

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

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

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

Вы можете использовать для этого jOOR.

class Foo {
    private final String value = "ABC";
}
class Bar {
    private final Foo foo = new Foo();
    public String value() {
        return org.joor.Reflect
            .on(this.foo)
            .field("value")
            .get();
    }
}
class BarTest {
    @Test
    void accessPrivateField() {
        Assertions.assertEquals(new Bar().value(), "ABC");
    }
}

Просто еще одно замечание об отражении: я заметил, что в некоторых особых случаях, когда несколько классов с одинаковыми именами существуют в разных пакетах, это отражение, используемое в верхнем ответе, может не выбрать правильный класс из объекта. Так что, если вы знаете, что такое 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);

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

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

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

и просмотрите свой плохо спроектированный проект:

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

Внутренне это зависит от размышлений.

Вы можете использовать @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Иерархия

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

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

Попробуйте обойти проблему для случая, класс которого вы хотите установить / получить данные - это один из ваших собственных классов.

Просто создайте 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-mapper.

Другие вопросы по тегам