Java Language
API di Reflection
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);
}
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:
- Crea istanza di classe usando
Class.forName
: chiama il costruttore predefinito - Richiamare
getDeclaredConstructor
della classe passando il tipo di parametri comeClass array
- Dopo aver ottenuto il costruttore, creare
newInstance
passando il valore del parametro comeObject 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.