Как прочитать значение частного поля из другого класса в 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, при доступе к полю, установке его как доступного и получении значения все Exception
s, хотя единственные отмеченные исключения, о которых вам нужно помнить, прокомментированы выше.
NoSuchFieldException
будет брошено, если вы попросите поле с именем, которое не соответствует объявленному полю.
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
IllegalAccessException
будет выброшено, если поле было недоступно (например, если оно является закрытым и не было сделано доступным из-за пропуска f.setAccessible(true)
линия.
RuntimeException
s, которые могут быть брошены, либо SecurityException
s (если JVM SecurityManager
не позволит вам изменить доступность поля), или IllegalArgumentException
s, если вы попытаетесь получить доступ к полю объекта, не относящегося к типу поля:
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.