Ricerca…


introduzione

Reflection viene comunemente utilizzato dai programmi che richiedono la possibilità di esaminare o modificare il comportamento di runtime delle applicazioni in esecuzione nella JVM. L'API Java Reflection viene utilizzata a tale scopo dove consente di ispezionare classi, interfacce, campi e metodi in fase di runtime, senza conoscere i loro nomi in fase di compilazione. Inoltre, rende possibile l'istanziazione di nuovi oggetti e l'invocazione di metodi mediante la riflessione.

Osservazioni

Prestazione

Tenere presente che la riflessione potrebbe ridurre le prestazioni, ma usarla solo quando l'attività non può essere completata senza riflessione.

Dal tutorial Java L'API Reflection :

Poiché la riflessione riguarda tipi risolti dinamicamente, non è possibile eseguire determinate ottimizzazioni della macchina virtuale Java. Di conseguenza, le operazioni di riflessione hanno prestazioni più lente rispetto alle controparti non riflettenti e dovrebbero essere evitate in sezioni di codice chiamate frequentemente in applicazioni sensibili alle prestazioni.

introduzione

Nozioni di base

L'API Reflection consente di controllare la struttura della classe del codice in fase di esecuzione e di richiamare il codice in modo dinamico. Questo è molto potente, ma è anche pericoloso poiché il compilatore non è in grado di determinare staticamente se le invocazioni dinamiche sono valide.

Un semplice esempio potrebbe essere quello di ottenere i costruttori e i metodi pubblici di una determinata classe:

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

Con questa informazione è possibile istanziare l'oggetto e chiamare dinamicamente diversi metodi.

Riflessione e tipi generici

Le informazioni di tipo generico sono disponibili per:

  • parametri del metodo, usando getGenericParameterTypes() .
  • metodo restituisce i tipi, usando getGenericReturnType() .
  • campi pubblici , usando getGenericType .

L'esempio seguente mostra come estrarre le informazioni di tipo generico in tutti e tre i casi:

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;
    }

}

Ciò risulta nell'output seguente:

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

Invocare un metodo

Utilizzando reflection, un metodo di un oggetto può essere richiamato durante il runtime.

L'esempio mostra come richiamare i metodi di un oggetto 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!"

Recupero e impostazione dei campi

Utilizzando l'API di Reflection, è possibile modificare o ottenere il valore di un campo in fase di esecuzione. Ad esempio, è possibile utilizzarlo in un'API per recuperare campi diversi in base a un fattore, come il sistema operativo. Puoi anche rimuovere i modificatori come final per consentire i campi di modifica finali.

Per fare ciò, dovrai utilizzare il metodo Class # getField () in un modo come quello mostrato di seguito:

// 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);

Ottenere i campi è molto più facile. Possiamo usare Field # get () e le sue varianti per ottenere il suo valore:

// 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);

Prendi nota di questo:

Quando si utilizza Class # getDeclaredField , utilizzarlo per ottenere un campo nella classe stessa:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Qui, HackMe#iAmDeclared è dichiarato campo. Tuttavia, HackMe#someState non è un campo dichiarato in quanto è ereditato dalla sua superclasse, Hacked.

Chiamare il costruttore

Ottenere l'oggetto Costruttore

È possibile ottenere la classe Constructor dall'oggetto Class questo modo:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

Dove la variabile constructors avrà un'istanza Constructor per ogni costruttore pubblico dichiarato nella classe.

Se si conoscono i tipi di parametri precisi del costruttore a cui si desidera accedere, è possibile filtrare il costruttore specifico. Il prossimo esempio restituisce il costruttore pubblico della classe data che prende come parametro un Integer :

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

Se nessun costruttore corrisponde agli argomenti di costruzione forniti, viene generata una NoSuchMethodException .

Nuova istanza usando l'oggetto Costruttore

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

Ottenere le costanti di un'enumerazione

Dando questa enumerazione come esempio:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

In Java una classe enum è come qualsiasi altra classe ma ha alcune costanti definite per i valori enum. Inoltre ha un campo che è un array che contiene tutti i valori e due metodi statici con nome values() e valueOf(String) .
Possiamo vedere questo se usiamo Reflection per stampare tutti i campi in questa classe

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

l'output sarà:

NORD
EST
SUD
OVEST
grado
ENUM $ VALORI

Quindi potremmo esaminare le classi enum con Reflection come qualsiasi altra classe. Ma l'API Reflection offre tre metodi specifici per enum.

controllo enum

Compass.class.isEnum();

Restituisce true per le classi che rappresentano un tipo enum.

recupero di valori

Object[] values = Compass.class.getEnumConstants();

Restituisce una matrice di tutti i valori enum come Compass.values ​​() ma senza bisogno di un'istanza.

controllo costante enum

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

Elenca tutti i campi classe che sono valori enum.

Ottieni la classe dato il suo nome (completo)

Data una String contenente il nome di una classe, è possibile accedere all'oggetto Class utilizzando Class.forName :

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
Java SE 1.2

Può essere specificato, se la classe deve essere inizializzata (secondo parametro di forName ) e quale ClassLoader deve essere utilizzato (terzo parametro):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

Chiama costruttori sovraccaricati usando la riflessione

Esempio: richiama diversi costruttori passando parametri rilevanti

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"});
        
    }
}

produzione:

Default constructor
Constructor :String => StackOverFlow

Spiegazione:

  1. Crea istanza di classe usando Class.forName : chiama il costruttore predefinito
  2. Richiamare getDeclaredConstructor della classe passando il tipo di parametri come Class array
  3. Dopo aver ottenuto il costruttore, creare newInstance passando il valore del parametro come Object array

API Misuse of Reflection per modificare variabili private e finali

La riflessione è utile quando è usata correttamente per scopi giusti. Utilizzando la reflection, è possibile accedere a variabili private e reinizializzare le variabili finali.

Di seguito è riportato lo snippet di codice, che non è consigliato.

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++;
    }
}

Produzione:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

Spiegazione:

Nel normale scenario, non è possibile accedere alle variabili private al di fuori della classe dichiarata (senza metodi getter e setter). final variabili final non possono essere riassegnate dopo l'inizializzazione.

Reflection rompe entrambe le barriere che possono essere sfruttate per modificare sia le variabili private che quelle finali come spiegato sopra.

field.setAccessible(true) è la chiave per ottenere la funzionalità desiderata.

Costruttore di chiamate di classe nidificata

Se si desidera creare un'istanza di una classe nidificata interna, è necessario fornire un oggetto classe della classe di inclusione come parametro aggiuntivo con 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"});
    }
}

Se la classe nidificata è statica, non avrai bisogno di questa istanza allegata.

Dynamic Proxies

I proxy dinamici non hanno molto a che fare con Reflection ma fanno parte dell'API. È fondamentalmente un modo per creare un'implementazione dinamica di un'interfaccia. Questo potrebbe essere utile quando si creano servizi di mockup.
Un proxy dinamico è un'istanza di un'interfaccia creata con un cosiddetto gestore di chiamata che intercetta tutte le chiamate di metodo e consente la gestione manuale della loro chiamata manualmente.

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();
    }
}

Il risultato di questo codice è questo:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

Evil Java hack con Reflection

L'API Reflection può essere utilizzata per modificare i valori dei campi privati ​​e finali anche nella libreria predefinita JDK. Questo potrebbe essere usato per manipolare il comportamento di alcune classi ben note come vedremo.

Cosa non è possibile

Iniziamo prima con l'unica limitazione, l'unico campo che non possiamo cambiare con Reflection. Questo è Java SecurityManager . È dichiarato in java.lang.System come

private static volatile SecurityManager security = null;

Ma non verrà elencato nella classe System se eseguiamo questo codice

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

Questo è dovuto al fieldFilterMap in sun.reflect.Reflection che mantiene la mappa stessa e il campo di sicurezza in System.class e li protegge da qualsiasi accesso con Reflection. Quindi non abbiamo potuto disattivare SecurityManager .

Stringhe folli

Ogni stringa Java è rappresentata da JVM come istanza della classe String . Tuttavia, in alcune situazioni, la JVM salva lo spazio dell'heap utilizzando la stessa istanza per le stringhe che lo sono. Ciò accade per i letterali stringa e anche per le stringhe che sono state "internate" chiamando String.intern() . Quindi se hai "hello" nel tuo codice più volte è sempre la stessa istanza di oggetto.

Le stringhe dovrebbero essere immutabili, ma è possibile utilizzare il riflesso "cattivo" per cambiarle. L'esempio seguente mostra come possiamo cambiare i caratteri in una stringa sostituendo il suo campo 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");
    }
}

Quindi questo codice stamperà "tu puzzi!"

1 = 42

La stessa idea potrebbe essere utilizzata con 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));
    }
}

Tutto è vero

E secondo questo post StackOverflow possiamo usare la riflessione per fare qualcosa di veramente malvagio.

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);
    }
}

Nota che ciò che stiamo facendo qui farà sì che la JVM si comporti in modi inesplicabili. Questo è molto pericoloso.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow