La Riflessione in Java La Riflessione in Java • La “Reflection” è la possibilità offerta ai programmi in un dato linguaggio di programmazione, ad accedere ad informazione sulla natura dei programmi stessi (“metadati”), fino a poter modificare se stessi. • In Java esistono delle classi che possono ottenere informazioni su una classe e sui suoi membri e manipolare oggetti. L19_Reflection 2 La riflessione è usata estensivamente in Java da tutti gli strumenti che devono operare su programmi e classi java (esempio editor avanzati-auto completamento del codice-, LOGGER, CLASSLOADER, application server, decompilatori, offuscatori… L19_Reflection 3 La Reflection in Java si realizza attraverso: • La classe “Class” nel package java.lang; • L’intero package java.lang.reflect che introduce le classi Method, Constructor e Field. Metadata per le classi → java.lang.Class Metadata per i costruttori → java.reflect.Constructor Metadata per i metodi → java.Method.Method Metadata per i campi → java.Method.Field L19_Reflection 4 java.lang.Class • Ad ogni classe Java corrisponde un oggetto della classe java.lang.Class • Attraverso i metodi della classe è possibile analizzare tutte le caratteristiche della classe La “Class” è la controparte degli oggetti della classe “java.lang.Object”, una vera e propria “meta-classe” L19_Reflection 5 • Proprietà statica “class” tutte le classi hanno una proprietà pubblica statica chiamata class che mantiene un riferimento all’oggetto di tipo java.lang.Class inizializzata automaticamente dalla JVM java.lang.Class classe = ….class • Il metodo getClass() di Object (alternativo alla proprietà statica class) consente di ottenere il riferimento all’oggetto di tipo java.lang.Class a partire da un oggetto invece che dalla classe Integer integer = new Integer(); java.lang.Class classe = integer.getClass(); L19_Reflection 6 Metodi di Class • Class c = Class.forName(s); Cerca ed eventualmente carica l’oggetto Class di una classe a partire dal suo nome (stringa) • c.newInstance() Crea un nuovo oggetto (Object) della classe e ne restituisce l’identificatore Metodi di “ispezione” • c.isInterface() • c.getModifiers() • c.getName() • c.getSuperclass() • c.getDeclaredConstructors() • c.getDeclaredFields() • c.getDeclaredMethods() • c.isArray() • c.isPrimitive() L19_Reflection 7 • Con Class è possibile costruire gli oggetti senza conoscere ed invocare il costruttore (a tempo di compilazione) Metodo basato sulla riflessione invocazioni successive di “forName” e “newInstance” per creare oggetti di classi arbitrarie: public void createObject(String s) { try { java.lang.Class c = java.lang.Class.forName(s); Object o = c.newInstance(); } catch (ClassNotFoundException e) { System.out.println(e); } catch (InstantiationException e) { System.out.println(e); } catch (IllegalAccessException e) { System.out.println(e); } L19_Reflection } 8 Il package java.lang.Reflect • classe Field: permette di scoprire e impostare valori di singoli campi • classe Method: consente di invocare metodi • classe Constructor: permette di creare nuovi oggetti. Altre classi accessorie sono Modifier, Array e ReflectPermission e Proxy. I nomi degli argomenti dei metodi non sono memorizzati nella classe, e non sono recuperabili via riflessione. L19_Reflection 9 • Attraverso l’oggetto class è possibile ottenere per una classe i riferimenti agli oggetti di tipo Field, Method,Constructor del package java.lang.Reflect, per analizzarne le caratteristiche, e utilizzarli dinamicamente (cambiare una proprietà, eseguire un metodo ecc.) Il package Reflect lavora sul bytecode L19_Reflection 10 • La riflessione consente di proporre in Java una caratteristica simile a quella dei puntatori a funzione del linguaggio C: – Invocazione indiretta di un metodo passato per nome. res = m.invoke(oggettoTarget, args) • m → istanza di Method, args è un array di Object (gli argomenti da passare al metodo) e res è un Object che rappresenta il risultato del metodo. • NB: invoke() è in grado di convertire automaticamente i tipi primitivi nei corrispondenti tipi "wrapper" e viceversa, così da poter chiamare anche metodi con parametri int, float, etc. • Problema efficienza. L19_Reflection 11 Esempio invocazione metodo con Reflection Object res=null; // recupero della classe dell’oggetto target Class c = ogg.getClass(); // preparazione array dei parametri formali Class[] parameters; ... // recupero del metodo Method m = null; try { m = c.getMethod(nomeMetodo, paramters); } catch (NoSuchMethodException e){} Object[] concreteParameters; //== operazioni sui parametri // invocazione del metodo try { res = m.invoke(ogg, concreteParameters); } catch(IllegalAccessException e){} catch(InvocationTargetException e){} L19_Reflection 12 Esempio (simulazione overriding/Overloading), identificazione metodo in una gerarchi di classi method findMethod (Class c, String MethodName, Class paramTypes) { Method method=null; while (cls!= null{ method.getDeclaratedMethod(methodName,paramTypes); } catch (NoSuchMethodExecprion ex) { cls=cls.getSuperClass(); } } return method; } } L19_Reflection 13 Il classLoader • Il meccanismo del classLoader esegue una fondamentale attività della JVM: carica il bytecode di una classe e crea l’oggetto class • ClassLoader usati dalla macchina virtuale sono SystemClassLoader (che carica la prima classe), ed altri più specializzati L19_Reflection 14 Il metodo principale di ClassLoader loadClass public Class loadClass(String name) • carica dal disco il bytecode della classe name, lo analizza e costruisce l’oggetto class corrispondente Il file .class della classe viene cercato usando il classpath • il bytecode di ciascun metodo viene verificato e viene assegnato lo spazio heap al componente (proprietà statiche) • inizializzazione: la macchina virtuale inizializza le proprietà statiche lo stesso principio viene utilizzato per il caricamento di risorse correlate (file che non contengono bytecode) L19_Reflection 15 I Proxy Dinamici Da Java 1.3 è supportata la creazione di Proxy dinamici Un proxy dinamico è una classe (proxy Class) che implementa una lista interfacce (Proxy interface) a run time, una istanza proxy è una istanza di classe proxy Un istanza proxy ha associato un oggetto di tipo “invocationHandler”, che implementa l'interfaccia proxy L'invocazione di un metodo di una interfaccia proxy da parte di una istanza proxy viene gestito dal metodo invoke() della “invocationHandle” della istanza L19_Reflection 16 Le classi Proxy sono create tramite il package “java.lang.reflect” Le classi proxy sono sottoclassi pubbliche, finali e NON astratte della classe java.lang.reflect.Proxy Una class proxy implementa esattamente l'interfaccia specificata alla sua creazione Per ottenere la lista delle interfacce di un classe, si possono usare i metodi getInterfaces() sulla oggetto Class per avere la lista delle interfacce (in ordine di creazione) L19_Reflection 17 Ciascuna classe proxy class ha un costruttore pubblico che prende un argomento, che un oggetto che implementa InvocationHandler Questo oggetto è utilizzato come InvocationHandler dall'istanza proxy creata. Si puo' ottenere una istanza Proxy invocando il metodo Proxy.newInstance() che ha lo stesso effetto di Proxy.getProxyClass() con il costruttore passato come invocvationHandler L19_Reflection 18 java.lang.reflect.Proxy public static Class getProxyClass(ClassLoader loader, Class[] interfaces) throws IllegalArgumentException Crea la proxy class specificata nel class loader e implementa la specifica interfaccia. Restituiesce l'oggetto Class Proxy protected Proxy(InvocationHandler ih) Costruisce una nuova instanza Proxy da una sottoclasse, (dynamic proxy ) con il valore specificato di InvocationHandler public static boolean isProxyClass(Class c) Verifica se l'oggetto c di tipo Class è una classe proxy class ottenuta con il metodo getProxyClass() o newProxyInstance() della classe Proxy L19_Reflection 19 public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler ih) throws IllegalArgumentException Ccrea una classe proxy definita nel classLoader, con la interfaccia specifica e l'invocation handler passato come argomento. Costruisce la referenza alla classe proxy e la restituisce. Proxy.newProxyInstance(cl, interfaces, ih); è equivalente a Proxy.getProxyClass(cl,interfaces).getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] {ih}); L19_Reflection 20 public static InvocationHandler getInvocationHandler (Object proxy) throws IllegalArgumentException Restituisce l'oggetto InvocationHandler per l'argomento (istanza di proxy dinamico) L19_Reflection 21 The java.lang.reflect.InvocationHandler Interface Ciascun Proxy possiede un oggetto che implementa l'interfaccia Invocation Handler. Quando si invoca un metodo di una istanza proxy, l'invocazione viene codificata e passata come argomento al metodo invoke del suo invocationHandler public Object invoke(Object proxy, Method method,Object[]args) throws Throwable Processa l'invocazione di un metodo di una istanza proxy e restituisce il risultato. Il parametro proxy è l'istanza proxy sui cui il metodo è invocato Il parametro method è l'istanza Method corrispondente al metodo dell'interfaccia invocato. Il parametro args è un array di oggetti contenenti i valori passati nella invocazione del metodo della istanza proxy, oppure null se se il metodo dell'ìinterfaccia non prevede argomenti. L19_Reflection 22 import java.lang.reflect.*; interface Worker { public String getName(); public void raiseSalary(double amount); public void raiseLevel(int amount); } class Employee implements worker { String name; double salary; int level; Employee() { salary=0; name=""; } Employee(String _name, double _salary,int _level) { name=_name; salary=_salary; level=_level; } public String getName() { return name; } public void raiseSalary(double amount) { salary+=amount; System.out.println("Employee:new salary:"+salary); } public void raiseLevel(int amount) { level+=amount; } } } L19_Reflection 23 class EmployeeHandler implements InvocationHandler { private Employee v; public EmployeeHandler(Employee v) {this.v = v;} public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { System.out.println("Employee Handler: Invoking " + m.getName()); return m.invoke(v, args); } } public class test { public static void main(String[] args) { Employee c = new Employee("A.Red",1000,1); ClassLoader cl = c.getClass().getClassLoader(); Worker w = (Worker) Proxy.newProxyInstance(cl, new Class[] {Worker.class}, new EmployeeHandler(c)); w.raiseSalary(200); } Employee Handler: Invoking raiseSalary Employee:new salary:1200.0 L19_Reflection 24 L19_Reflection 25 Quali sono i vantaggi di usare un proxy dinamico, visto che bisogna scrivere una classe InvocationHandler? Ottenere effettivamente proxy dinamici generici e ottenere un meccanismo di delegazione generica Es Logger Proxy public class LoggedWorker implements Worker { private Worker w; public LoggedEmployee(Worker w) {this.w = w;} public void raiseSalary() { System.out.println("Log Entry: Worker " + w.getWorker() + " raiseSalary "); w.raiseSalary(); } // altri metodi. } L19_Reflection 26 Soluzione logger generica, non da implementare caso caso import java.lang.reflect.*; /** * Class GenericLogger. */ public class GenericLogger implements InvocationHandler { private Object target; public GenericLogger(Object target) {this.target = target;} public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { System.out.println("Generic Logger Entry: Invoking " + m.getName()); return m.invoke(target, args); } } UTILIZZO Worker w2 = (Worker) Proxy.newProxyInstance(cl, new Class[] {Worker.class}, new GenericLogger(c)); L19_Reflection 27