Получить объявленные поля java.lang.reflect.Fields в jdk12
В java8 было возможно получить доступ к полям класса java.lang.reflect.Fields, используя например
Field.class.getDeclaredFields();
В java12 (начиная с java9?) Это возвращает только пустой массив. Это не меняется даже с
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
задавать.
Есть идеи как этого добиться? (Исходя из того, что это может быть плохой идеей, я хочу иметь возможность изменять поле "static final" в моем коде во время тестирования junit с помощью отражения. Это стало возможным с помощью java8 путем изменения "модификаторов").
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(myfield, myfield.getModifiers() & ~Modifier.FINAL);
)
4 ответа
Причина, по которой это больше не работает в Java 12, связана с JDK-8210522. Этот CSR говорит:
Резюме
Базовое отражение имеет механизм фильтрации, позволяющий скрыть чувствительные к безопасности и целостности поля и методы от классов getXXXField(s) и getXXXMethod(s). Механизм фильтрации был использован для нескольких выпусков, чтобы скрыть чувствительные к безопасности поля, такие как System.security и Class.classLoader.
Этот CSR предлагает расширить фильтры, чтобы скрыть поля от ряда классов с высокой степенью безопасности в java.lang.reflect и java.lang.invoke.
проблема
Многие из классов в пакетах java.lang.reflect и java.lang.invoke имеют закрытые поля, которые при прямом доступе могут поставить под угрозу среду выполнения или привести к сбою виртуальной машины. В идеале все непубличные / незащищенные поля классов в java.base должны быть отфильтрованы по отражению ядра и недоступны для чтения / записи через небезопасный API, но в данный момент мы не находимся поблизости от этого. В то же время механизм фильтрации используется в качестве помощи полосы.
Решение
Расширить фильтр на все поля в следующих классах:
java.lang.ClassLoader java.lang.reflect.AccessibleObject java.lang.reflect.Constructor java.lang.reflect.Field java.lang.reflect.Method
и частные поля в java.lang.invoke.MethodHandles.Lookup, которые используются для класса поиска и режима доступа.
Спецификация
Изменений в спецификации нет, это фильтрация непубличных / незащищенных полей, на которые не следует полагаться ничего за пределами java.base. Ни один из классов не является сериализуемым.
В основном они отфильтровывают поля java.lang.reflect.Field
так что вы не можете злоупотреблять ими - как вы сейчас пытаетесь. Вы должны найти другой способ сделать то, что вам нужно; Похоже, что ответ Юджина дает хотя бы один вариант.
Примечание. Приведенный выше CSR указывает на то, что конечной целью является предотвращение любого отражающего доступа к внутреннему коду внутри java.base
модуль. Этот механизм фильтрации, по-видимому, влияет только на Core Reflection API, и его можно обойти, используя Invoke API. Я не совсем уверен, как связаны эти два API, поэтому, если это нежелательное поведение - за исключением сомнительности изменения статического конечного поля - кто-то должен отправить отчет об ошибке (сначала проверьте существующий). Другими словами, используйте приведенный ниже взлом на свой страх и риск; попробуйте найти другой способ сделать то, что вам нужно в первую очередь.
Тем не менее, похоже, что вы все еще можете взломать modifiers
поле, по крайней мере, в OpenJDK 12.0.1, используя java.lang.invoke.VarHandle
,
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public final class FieldHelper {
private static final VarHandle MODIFIERS;
static {
try {
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
public static void makeNonFinal(Field field) {
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
}
}
}
Следующее использует вышеизложенное, чтобы изменить статический финал EMPTY_ELEMENTDATA
поле внутри ArrayList
, Это поле используется, когда ArrayList
инициализируется с емкостью 0
, Конечным результатом является созданный ArrayList
содержит элементы без добавления каких-либо элементов.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
var newEmptyElementData = new Object[]{"Hello", "World!"};
updateEmptyElementDataField(newEmptyElementData);
var list = new ArrayList<>(0);
// toString() relies on iterator() which relies on size
var sizeField = list.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(list, newEmptyElementData.length);
System.out.println(list);
}
private static void updateEmptyElementDataField(Object[] array) throws Exception {
var field = ArrayList.class.getDeclaredField("EMPTY_ELEMENTDATA");
FieldHelper.makeNonFinal(field);
field.setAccessible(true);
field.set(null, array);
}
}
Выход:
[Hello, World!]
использование --add-opens
как необходимо.
Я нашел способ, и он работал на JDK 8, 11, 17.
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
Field modifiers = null;
for (Field each : fields) {
if ("modifiers".equals(each.getName())) {
modifiers = each;
break;
}
}
assertNotNull(modifiers);
Не забудьте установить следующие аргументы при использовании JDK 11 или выше:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
Ты не можешь Это было изменение сделано специально.
Например, вы можете использовать PowerMock
И его @PrepareForTest
- под капотом он использует javassist (манипулирование байт-кодом), если вы хотите использовать это для целей тестирования. Это именно то, что предлагает сделать эта ошибка в комментариях.
Другими словами, так как java-12
- нет никакого способа получить доступ к этому через ванильную Java.
Это работает в JDK 17.
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/**
* @author Arnah
* @since Feb 21, 2021
**/
public class FieldUtil{
private static Unsafe unsafe;
static{
try{
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
}catch(Exception ex){
ex.printStackTrace();
}
}
public static void setFinalStatic(Field field, Object value) throws Exception{
Object fieldBase = unsafe.staticFieldBase(field);
long fieldOffset = unsafe.staticFieldOffset(field);
unsafe.putObject(fieldBase, fieldOffset, value);
}
}
public class YourClass{
public static final int MAX_ITEM_ROWS = 35_000;
}
FieldUtil.setFinalStatic(YourClass.class.getDeclaredField("MAX_ITEM_ROWS"), 1);