Java Language
API de réflexion
Recherche…
Introduction
Reflection est couramment utilisé par les programmes qui doivent pouvoir examiner ou modifier le comportement d'exécution des applications exécutées dans la JVM. L'API Java Reflection est utilisée à cette fin, où elle permet d'inspecter les classes, les interfaces, les champs et les méthodes à l'exécution, sans connaître leurs noms au moment de la compilation. Et il permet également d'instancier de nouveaux objets et d'invoquer des méthodes utilisant la réflexion.
Remarques
Performance
Gardez à l'esprit que la réflexion peut diminuer les performances, ne l'utilisez que si votre tâche ne peut être complétée sans réflexion.
A partir du tutoriel Java, l' API Reflection :
Étant donné que la réflexion implique des types résolus dynamiquement, certaines optimisations de la machine virtuelle Java ne peuvent pas être effectuées. Par conséquent, les opérations de réflexion ont des performances plus lentes que leurs homologues non réfléchissantes et doivent être évitées dans les sections de code appelées fréquemment dans les applications sensibles aux performances.
introduction
Les bases
L'API de réflexion permet de vérifier la structure de classe du code à l'exécution et d'appeler le code dynamiquement. Ceci est très puissant, mais il est également dangereux car le compilateur n'est pas capable de déterminer statiquement si les appels dynamiques sont valides.
Un exemple simple serait d'obtenir les constructeurs publics et les méthodes d'une classe donnée:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;
Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents
Avec cette information, il est possible d'injecter l'objet et d'appeler différentes méthodes dynamiquement.
Réflexion et types génériques
Les informations de type génériques sont disponibles pour:
- paramètres de méthode, en utilisant
getGenericParameterTypes()
. - types de retour de méthode, en utilisant
getGenericReturnType()
. - champs publics , en utilisant
getGenericType
.
L'exemple suivant montre comment extraire les informations de type générique dans les trois cas:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class GenericTest {
public static void main(final String[] args) throws Exception {
final Method method = GenericTest.class.getMethod("testMethod", Map.class);
final Field field = GenericTest.class.getField("testField");
System.out.println("Method parameter:");
final Type parameterType = method.getGenericParameterTypes()[0];
displayGenericType(parameterType, "\t");
System.out.println("Method return type:");
final Type returnType = method.getGenericReturnType();
displayGenericType(returnType, "\t");
System.out.println("Field type:");
final Type fieldType = field.getGenericType();
displayGenericType(fieldType, "\t");
}
private static void displayGenericType(final Type type, final String prefix) {
System.out.println(prefix + type.getTypeName());
if (type instanceof ParameterizedType) {
for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
displayGenericType(subtype, prefix + "\t");
}
}
}
public Map<String, Map<Integer, List<String>>> testField;
public List<Number> testMethod(final Map<String, Double> arg) {
return null;
}
}
Cela se traduit par la sortie suivante:
Method parameter:
java.util.Map<java.lang.String, java.lang.Double>
java.lang.String
java.lang.Double
Method return type:
java.util.List<java.lang.Number>
java.lang.Number
Field type:
java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
java.lang.String
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
java.lang.Integer
java.util.List<java.lang.String>
java.lang.String
Invoquer une méthode
En utilisant la réflexion, une méthode d'un objet peut être appelée pendant l'exécution.
L'exemple montre comment appeler les méthodes d'un objet String
.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
String s = "Hello World!";
// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"
// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"
Champs Obtenir et définir
À l'aide de l'API Reflection, il est possible de modifier ou d'obtenir la valeur d'un champ à l'exécution. Par exemple, vous pouvez l'utiliser dans une API pour extraire différents champs basés sur un facteur, comme le système d'exploitation. Vous pouvez également supprimer des modificateurs comme final
pour permettre la modification des champs qui sont finaux.
Pour ce faire, vous devrez utiliser la méthode Class # getField () de la manière indiquée ci-dessous:
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Get the field in class Field "modifiers". Note that it does not
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");
// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);
// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();
// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;
// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);
// Set it to be accessible. This overrides normal Java
// private/protected/package/etc access control checks.
nameField.setAccessible(true);
// Set the value of "NAME" here. Note the null argument.
// Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");
// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);
Obtenir des champs est beaucoup plus facile. Nous pouvons utiliser Field # get () et ses variantes pour obtenir sa valeur:
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Set accessible for private fields
nameField.setAccessible(true);
// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);
Notez ceci:
Lorsque vous utilisez Class # getDeclaredField , utilisez-le pour obtenir un champ dans la classe elle-même:
class HackMe extends Hacked {
public String iAmDeclared;
}
class Hacked {
public String someState;
}
Ici, HackMe#iAmDeclared
est déclaré champ. Cependant, HackMe#someState
n'est pas un champ déclaré car il est hérité de sa super-classe, Hacked.
Appel constructeur
Obtenir l'objet constructeur
Vous pouvez obtenir la classe Constructor
partir de l'objet Class
comme ceci:
Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();
Où la variable constructors
aura une instance de Constructor
pour chaque constructeur public déclaré dans la classe.
Si vous connaissez les types de paramètres précis du constructeur auquel vous souhaitez accéder, vous pouvez filtrer le constructeur spécifique. L'exemple suivant retourne le constructeur public de la classe donnée qui prend un Integer
comme paramètre:
Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});
Si aucun constructeur ne correspond aux arguments du constructeur, une NoSuchMethodException
est lancée.
Nouvelle instance utilisant un objet constructeur
Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));
Obtenir les constantes d'une énumération
Donner cette énumération comme exemple:
enum Compass {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270);
private int degree;
Compass(int deg){
degree = deg;
}
public int getDegree(){
return degree;
}
}
En Java, une classe enum est comme toute autre classe mais possède des constantes définies pour les valeurs enum. De plus, il contient un champ qui est un tableau contenant toutes les valeurs et deux méthodes statiques avec des values()
name values()
et valueOf(String)
.
Nous pouvons le voir si nous utilisons Reflection pour imprimer tous les champs de cette classe
for(Field f : Compass.class.getDeclaredFields())
System.out.println(f.getName());
le résultat sera:
NORD
EST
SUD
OUEST
degré
ENUM $ VALUES
Nous pourrions donc examiner les classes enum avec Reflection comme toute autre classe. Mais l'API Reflection propose trois méthodes spécifiques à l'énumération.
chèque enum
Compass.class.isEnum();
Renvoie true pour les classes qui représentent un type enum.
récupérer des valeurs
Object[] values = Compass.class.getEnumConstants();
Retourne un tableau de toutes les valeurs enum comme Compass.values () mais sans avoir besoin d'une instance.
enum contrôle constant
for(Field f : Compass.class.getDeclaredFields()){
if(f.isEnumConstant())
System.out.println(f.getName());
}
Répertorie tous les champs de classe qui sont des valeurs enum.
Obtenir la classe en fonction de son nom (entièrement qualifié)
Étant donné une String
contenant le nom d'une classe, son objet Class
est accessible à l'aide de Class.forName
:
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
Il peut être spécifié si la classe doit être initialisée (deuxième paramètre de forName
) et quel ClassLoader
doit être utilisé (troisième paramètre):
ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
Appeler les constructeurs surchargés en utilisant la réflexion
Exemple: appel de différents constructeurs en passant des paramètres pertinents
import java.lang.reflect.*;
class NewInstanceWithReflection{
public NewInstanceWithReflection(){
System.out.println("Default constructor");
}
public NewInstanceWithReflection( String a){
System.out.println("Constructor :String => "+a);
}
public static void main(String args[]) throws Exception {
NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
}
}
sortie:
Default constructor
Constructor :String => StackOverFlow
Explication:
- Créer une instance de classe en utilisant
Class.forName
: elle appelle le constructeur par défaut - Appelez
getDeclaredConstructor
de la classe en transmettant le type de paramètres en tant queClass array
- Après avoir obtenu le constructeur, créez
newInstance
en transmettant la valeur du paramètre en tant queObject array
Mauvaise utilisation de l'API Reflection pour modifier les variables privées et finales
La réflexion est utile lorsqu'elle est correctement utilisée à bon escient. En utilisant la réflexion, vous pouvez accéder aux variables privées et réinitialiser les variables finales.
Voici l'extrait de code, qui n'est pas recommandé.
import java.lang.reflect.*;
public class ReflectionDemo{
public static void main(String args[]){
try{
Field[] fields = A.class.getDeclaredFields();
A a = new A();
for ( Field field:fields ) {
if(field.getName().equalsIgnoreCase("name")){
field.setAccessible(true);
field.set(a, "StackOverFlow");
System.out.println("A.name="+field.get(a));
}
if(field.getName().equalsIgnoreCase("age")){
field.set(a, 20);
System.out.println("A.age="+field.get(a));
}
if(field.getName().equalsIgnoreCase("rep")){
field.setAccessible(true);
field.set(a,"New Reputation");
System.out.println("A.rep="+field.get(a));
}
if(field.getName().equalsIgnoreCase("count")){
field.set(a,25);
System.out.println("A.count="+field.get(a));
}
}
}catch(Exception err){
err.printStackTrace();
}
}
}
class A {
private String name;
public int age;
public final String rep;
public static int count=0;
public A(){
name = "Unset";
age = 0;
rep = "Reputation";
count++;
}
}
Sortie:
A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25
Explication:
Dans un scénario normal, private
variables private
ne sont pas accessibles en dehors de la classe déclarée (sans les méthodes getter et setter). final
variables final
ne peuvent pas être réaffectées après l'initialisation.
Reflection
brise les deux obstacles peut être utilisée pour modifier les variables privées et finales comme expliqué ci-dessus.
field.setAccessible(true)
est la clé pour obtenir la fonctionnalité souhaitée.
Appelez le constructeur de la classe imbriquée
Si vous souhaitez créer une instance d'une classe imbriquée interne, vous devez fournir un objet de classe de la classe englobante en tant que paramètre supplémentaire avec Class # getDeclaredConstructor .
public class Enclosing{
public class Nested{
public Nested(String a){
System.out.println("Constructor :String => "+a);
}
}
public static void main(String args[]) throws Exception {
Class<?> clazzEnclosing = Class.forName("Enclosing");
Class<?> clazzNested = Class.forName("Enclosing$Nested");
Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
}
}
Si la classe imbriquée est statique, vous n'aurez pas besoin de cette instance englobante.
Proxies dynamiques
Les proxys dynamiques n'ont pas grand chose à voir avec Reflection, mais ils font partie de l'API. C'est essentiellement un moyen de créer une implémentation dynamique d'une interface. Cela peut être utile lors de la création de services de maquette.
Un proxy dynamique est une instance d'une interface créée avec un gestionnaire d'invocation appelé qui intercepte tous les appels de méthode et permet la gestion manuelle de leur appel.
public class DynamicProxyTest {
public interface MyInterface1{
public void someMethod1();
public int someMethod2(String s);
}
public interface MyInterface2{
public void anotherMethod();
}
public static void main(String args[]) throws Exception {
// the dynamic proxy class
Class<?> proxyClass = Proxy.getProxyClass(
ClassLoader.getSystemClassLoader(),
new Class[] {MyInterface1.class, MyInterface2.class});
// the dynamic proxy class constructor
Constructor<?> proxyConstructor =
proxyClass.getConstructor(InvocationHandler.class);
// the invocation handler
InvocationHandler handler = new InvocationHandler(){
// this method is invoked for every proxy method call
// method is the invoked method, args holds the method parameters
// it must return the method result
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.equals("someMethod1")){
System.out.println("someMethod1 was invoked!");
return null;
}
if(methodName.equals("someMethod2")){
System.out.println("someMethod2 was invoked!");
System.out.println("Parameter: " + args[0]);
return 42;
}
if(methodName.equals("anotherMethod")){
System.out.println("anotherMethod was invoked!");
return null;
}
System.out.println("Unkown method!");
return null;
}
};
// create the dynamic proxy instances
MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);
// and invoke some methods
i1.someMethod1();
i1.someMethod2("stackoverflow");
i2.anotherMethod();
}
}
Le résultat de ce code est le suivant:
someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!
Mal Java hacks avec réflexion
L'API de réflexion peut être utilisée pour modifier les valeurs des champs privé et final, même dans la bibliothèque par défaut du JDK. Cela pourrait être utilisé pour manipuler le comportement de certaines classes bien connues comme nous le verrons.
Ce qui n'est pas possible
Laisser commencer par la seule limitation signifie que le seul champ que nous ne pouvons pas modifier avec Reflection. C'est le Java SecurityManager
. Il est déclaré dans java.lang.System comme
private static volatile SecurityManager security = null;
Mais il ne sera pas répertorié dans la classe System si nous exécutons ce code
for(Field f : System.class.getDeclaredFields())
System.out.println(f);
C'est à cause de fieldFilterMap
dans sun.reflect.Reflection
qui contient la carte elle-même et le champ de sécurité dans System.class
et les protège contre tout accès avec Reflection. Nous n'avons donc pas pu désactiver le SecurityManager
.
Cordes folles
Chaque chaîne Java est représentée par la machine virtuelle Java en tant qu'instance de la classe String
. Toutefois, dans certaines situations, la machine virtuelle Java enregistre l'espace mémoire en utilisant la même instance pour les chaînes qui le sont. Cela se produit pour les littéraux de chaîne, ainsi que pour les chaînes qui ont été "internées" en appelant String.intern()
. Donc, si vous avez "hello"
dans votre code plusieurs fois, c'est toujours la même instance d'objet.
Les chaînes sont supposées être immuables, mais il est possible d'utiliser une réflexion "mauvaise" pour les changer. L'exemple ci-dessous montre comment nous pouvons modifier les caractères d'une chaîne en remplaçant son champ de value
.
public class CrazyStrings {
static {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("hello", "you stink!".toCharArray());
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println("hello");
}
}
Donc, ce code imprimera "vous pue!"
1 = 42
La même idée pourrait être utilisée avec la classe Integer
public class CrazyMath {
static {
try {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.setInt(Integer.valueOf(1), 42);
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println(Integer.valueOf(1));
}
}
Tout est vrai
Et selon ce post stackoverflow, nous pouvons utiliser la réflexion pour faire quelque chose de vraiment mal.
public class Evil {
static {
try {
Field field = Boolean.class.getField("FALSE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, true);
} catch (Exception e) {
}
}
public static void main(String args[]){
System.out.format("Everything is %s", false);
}
}
Notez que ce que nous faisons ici va provoquer un comportement inexplicable de la JVM. C'est très dangereux.