Как получить значение закрытого поля из другого класса в Java?
У меня есть плохо спроектированный класс в стороннем JAR
, и мне нужно получить доступ к одному из его приватных полей. Например, возникает вопрос, почему я должен выбирать приватное поле и нужно ли это делать?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
Как я могу использовать рефлексию, чтобы получить значение stuffIWant
?
5 ответ(ов)
Чтобы получить доступ к приватным полям, вам необходимо извлекать их из объявленных полей класса и сделать их доступными:
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
ИСПРАВЛЕНИЕ: как отметил aperkins, доступ к полю, установка его в доступный вид и извлечение значения могут вызывать Exception
, хотя единственные проверяемые исключения, о которых следует помнить, приведены выше.
NoSuchFieldException
будет выброшено, если вы попытаетесь получить поле по имени, которое не соответствует ни одному из объявленных полей.
obj.getClass().getDeclaredField("misspelled"); //выдаст NoSuchFieldException
IllegalAccessException
будет выброшено, если поле недоступно (например, если оно является приватным и вы не установили доступность с помощью строки f.setAccessible(true)
).
Среди RuntimeException
, которые могут быть выброшены, есть либо SecurityException
(если SecurityManager
JVM не позволит вам изменить доступность поля), либо IllegalArgumentException
, если вы попробуете получить доступ к полю объекта, который не соответствует типу класса этого поля:
f.get("BOB"); //выдаст IllegalArgumentException, так как String имеет неправильный тип
Одним из других вариантов, который еще не был упомянут: используйте Groovy. Groovy позволяет вам получать доступ к приватным переменным экземпляра благодаря особенностям дизайна языка. Независимо от того, есть ли у вас метод-геттер для поля, вы можете просто воспользоваться следующим кодом:
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
Таким образом, Groovy предоставляет гибкие возможности работы с приватными членами классов, что может быть полезно в ситуациях, когда вам нужно обойти ограничения доступа.
Как упоминает oxbow_lakes, вы можете использовать рефлексию, чтобы обойти ограничения доступа (при условии, что ваш SecurityManager это позволит).
С учетом сказанного, если этот класс так плохо спроектирован, что вам приходится прибегать к таким уловкам, возможно, стоит поискать альтернативу. Конечно, этот небольшой хак может сэкономить вам пару часов сейчас, но сколько это будет стоить вам в будущем?
Чтобы использовать фреймворк оптимизации Soot для прямого изменения байт-кода, вам нужно выполнить несколько шагов.
Подключение Soot к вашему проекту: Сначала добавьте Soot в зависимости вашего проекта, например, через Maven:
<dependency> <groupId>org.picocontainer</groupId> <artifactId>soot</artifactId> <version>4.3.0</version> <!-- Убедитесь, что используете актуальную версию --> </dependency>
Конфигурация Soot: Перед началом важно настроить Soot. Это включает в себя указание классов, которые предстоит анализировать и изменять. Вы можете начать с основной настройки, например:
String[] sootArgs = { "-process-dir", "путь/к/вашему/коду", "-d", "путь/для/выходных/файлов", "-src-prec", "java", "-target", "1.8", // или другая версия Java }; soot.Main.main(sootArgs);
Изменение байт-кода: Для внесения изменений в байт-код вам нужно будет создать анализатор и визиторы (visitors). Например, чтобы изменить определённый метод, вы можете использовать
Transformer
в Soot:public class MyBodyTransformer extends BodyTransformer { @Override protected void internalTransform(Body body, String phase, Map<String, String> options) { // Логика для изменения байт-кода } }
Запуск и генерация новых классов: После внесения всех необходимых изменений запустите процесс обработки, и Soot сгенерирует изменённые классы в указанной вами директории.
Обратите внимание, что Soot полностью написан на Java и поддерживает новые версии Java, что делает его гибким инструментом для работы с байт-кодом. Для более глубокого понимания стоит ознакомиться с документацией Soot, которая содержит множество примеров и подробности по различным аспектам работы с фреймворком.
Вам необходимо реализовать метод, который будет получать поле класса по его имени, включая поля родительских классов. Вот пример, как это можно сделать:
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) {
// Если поле не найдено, пробуем у родителя
} catch (Exception e) {
throw new IllegalArgumentException(
"Не удалось получить доступ к полю " + cls.getName() + "." + fieldName, e);
}
}
throw new IllegalArgumentException(
"Поле " + cls.getName() + "." + fieldName + " не найдено");
}
Объяснение:
- Метод
getField
принимает два параметра: классcls
и строкуfieldName
, которая представляет собой имя поля, которое нужно найти. - Мы начинаем с текущего класса и перебираем все его суперклассы с помощью цикла.
- В блоке
try
мы пытаемся получить доступ к полю с указанным именем. Если поле найдено, устанавливаем его доступность черезsetAccessible(true)
, чтобы иметь возможность работать с приватными полями, и возвращаем его. - Если поле не найдено (
NoSuchFieldException
), то продолжаем поиск в суперклассе. - Если произошла какая-то другая ошибка, выбрасываем
IllegalArgumentException
с детальным сообщением об ошибке. - Если после перебора всех суперклассов поле так и не было найдено, также выбрасываем
IllegalArgumentException
с соответствующим сообщением.
Этот подход позволяет гибко работать с полями, даже если они объявлены в родительских классах.
Изменение приватного статического финального поля с помощью рефлексии в Java
Как создать обобщённый массив в Java?
Получить обобщённый тип класса во время выполнения
Имеет ли Python "приватные" переменные в классах?
Вывод типа с помощью рефлексии для лямбд в Java 8