En Java8, il était possible d'accéder aux champs de la classe Java.lang.reflect.Fields en utilisant par ex.
Field.class.getDeclaredFields();
En Java12 (en commençant par Java9?), Cela ne renvoie qu'un tableau vide. Cela ne change pas même avec
--add-opens Java.base/Java.lang.reflect=ALL-UNNAMED
ensemble.
Des idées pour y parvenir? (Mis à part le fait que cela pourrait être une mauvaise idée, je veux pouvoir changer un champ "final statique" dans mon code pendant les tests junit via la réflexion. Cela a été possible avec Java8 en changeant les "modificateurs"
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(myfield, myfield.getModifiers() & ~Modifier.FINAL);
)
La raison pour laquelle cela ne fonctionne plus dans Java 12 est due à JDK-8210522 . Ce CSR dit:
Résumé
La réflexion centrale a un mécanisme de filtrage pour masquer les champs et méthodes sensibles à la sécurité et à l'intégrité des classes getXXXField (s) et getXXXMethod (s). Le mécanisme de filtrage a été utilisé pour plusieurs versions pour masquer les champs sensibles à la sécurité tels que System.security et Class.classLoader.
Ce CSR propose d'étendre les filtres pour masquer les champs d'un certain nombre de classes hautement sensibles à la sécurité dans Java.lang.reflect et Java.lang.invoke.
Problème
De nombreuses classes dans les packages Java.lang.reflect et Java.lang.invoke ont des champs privés qui, s'ils sont accessibles directement, compromettront l'exécution ou planteront la machine virtuelle. Idéalement, tous les champs de classes non publics/non protégés dans Java.base seraient filtrés par réflexion de base et ne seraient pas lisibles/inscriptibles via l'API Unsafe, mais nous n'en sommes pas du tout proches pour le moment. En attendant, le mécanisme de filtrage est utilisé comme pansement.
Solution
Étendez le filtre à tous les champs des classes suivantes:
Java.lang.ClassLoader Java.lang.reflect.AccessibleObject Java.lang.reflect.Constructor Java.lang.reflect.Field Java.lang.reflect.Method
et les champs privés dans Java.lang.invoke.MethodHandles.Lookup qui sont utilisés pour la classe de recherche et le mode d'accès.
Spécification
Il n'y a pas de changement de spécification, c'est le filtrage des champs non publics/non protégés sur lesquels rien en dehors de Java.base ne devrait s'appuyer. Aucune des classes n'est sérialisable.
Fondamentalement, ils filtrent les champs de Java.lang.reflect.Field
afin que vous ne puissiez pas en abuser - comme vous essayez actuellement de le faire. Vous devriez trouver une autre façon de faire ce dont vous avez besoin; le réponse d'Eugene semble fournir au moins une option.
Remarque : Le CSR ci-dessus indique que le but ultime est d'empêcher tout accès réfléchi au code interne dans le Java.base
module. Cependant, ce mécanisme de filtrage semble affecter uniquement l'API Core Reflection et peut être contourné en utilisant l'API Invoke. Je ne sais pas exactement comment les deux API sont liées, donc si ce n'est pas le comportement souhaité - au-delà du doute de changer un champ final statique - quelqu'un devrait soumettre un rapport de bogue (vérifier s'il existe un Un premier). En d'autres termes, utilisez le hack ci-dessous à vos risques et périls ; essayez de trouver une autre façon de faire ce dont vous avez besoin en premier.
Cela dit, il semble que vous pouvez toujours pirater le champ modifiers
, au moins dans OpenJDK 12.0.1, en utilisant 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);
}
}
}
Ce qui suit utilise ce qui précède pour modifier la finale statique EMPTY_ELEMENTDATA
champ à l'intérieur ArrayList
. Ce champ est utilisé lorsqu'un ArrayList
est initialisé avec une capacité de 0
. Le résultat final est que le ArrayList
créé contient des éléments sans avoir réellement ajouté d'éléments.
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);
}
}
Production:
[Hello, World!]
Utilisation --add-opens
le cas échéant.
Tu ne peux pas. C'était un changement fait exprès.
Par exemple, vous pouvez utiliser PowerMock
et c'est @PrepareForTest
- sous le capot, il utilise javassist (manipulation de bytecode) si vous souhaitez l'utiliser à des fins de test. C'est exactement ce que ce bug dans les commentaires suggère de faire.
En d'autres termes, puisque Java-12
- il n'y a aucun moyen d'y accéder via Vanilla Java.