Contenuto
Introduzione
Dynamic Proxy API
Serializzazione
Esempi
Introduzione
Una dinamica di classe proxy è una classe che implementa una lista di interfacce specificate in fase di runtime in modo tale che un metodo invocationthrough una delle interfacce di un’istanza della classe beencoded e spediti in un altro oggetto, attraverso una uniforminterface. Pertanto, una classe proxy dinamica può essere utilizzata per creare un oggetto proxy atype-safe per un elenco di interfacce senza richiedere la pre-generazione della classe proxy, ad esempio con strumenti in fase di compilazione.Le invocazioni di metodo su un’istanza di una classe proxy dinamica vengono inviate a un singolo metodo nel invocationhandler dell’istanza e sono codificate con un oggetto java.lang.reflect.Method
che identifica il metodo richiamato e una matrice di tipo Object
contenente gli argomenti.
Le classi proxy dinamiche sono utili per un’applicazione o una libreria che deve fornire un invio riflettente sicuro per il tipo di oggetti invocationson che presentano API di interfaccia. Ad esempio, un applicationcan utilizzare una classe proxy dinamica per creare un oggetto che implementsmultiple arbitrary event listener interfaces interfaces interfaces thatextend java.util.EventListener
to per elaborare una varietyof eventi di diversi tipi in modo uniforme, come bylogging tutti questi eventi in un file.
API della classe proxy dinamica
Una classe proxy dinamica (denominata semplicemente proxyclass di seguito) è una classe che implementa un elenco di interfacesspecified in fase di runtime quando viene creata la classe.
Un’interfaccia proxy è un’interfaccia che èimplementata da una classe proxy.
Un’istanza proxy è un’istanza di un proxyclass.
Creazione di una classe Proxy
Le classi proxy, così come le loro istanze, vengono create usandoi metodi statici della classe java.lang.riflettere.Proxy.
Il metodo Proxy.getProxyClass
restituisce l’oggettojava.lang.Class
per una classe proxy data un classloader e una matrice di interfacce. La classe proxy sarà definedin il caricatore di classe specificato e implementerà tutte le interfacce thesupplied. Se una classe proxy per la stessa permutazione diinterfaces è già stata definita nel caricatore di classe, verrà restituita la classe proxy esistente; altrimenti, una classe proxy per quelle interfacce verrà generata dinamicamente e definita nel caricatore di classe.
Esistono diverse restrizioni sui parametri che possono essere superati a Proxy.getProxyClass
:
- Tutti gli oggetti
Class
nell’arrayinterfaces
devono rappresentare interfacce, non classi o tipi primitivi. - Non ci sono due elementi nell’array
interfaces
che possono riferirsi a oggetti identiciClass
. - Tutti i tipi di interfaccia devono essere visibili per nome tramite il caricatore di classi specificato. In altre parole, per class loader
cl
e ogni interfacciai
, la seguente espressione deve essere true:Class.forName(i.getName(), false, cl) == i
- Tutte le interfacce non pubbliche devono essere nello stesso pacchetto;altrimenti, non sarebbe possibile per la classe proxy implementare tutte le interfacce, indipendentemente dal pacchetto in cui è definito.
- Per qualsiasi insieme di metodi membro delle interfacce specificate cheavere la stessa firma:
- Se il tipo restituito di uno qualsiasi dei metodi è un tipo primitivo orvoid, tutti i metodi devono avere lo stesso tipo restituito.
- In caso contrario, uno dei metodi deve avere un tipo di ritorno che èassignable a tutti i tipi di ritorno del resto dei themethods.
- La classe proxy risultante non deve superare i limiti imposti onclasses dalla macchina virtuale. Ad esempio, la VM può limitare il numero di interfacce che una classe può implementare a 65535; in questo caso, la dimensione dell’array
interfaces
non deve superare 65535.
Se una di queste restrizioni viene violata,Proxy.getProxyClass
genererà unIllegalArgumentException
. Se l’argomento dell’arrayinterfaces
o uno qualsiasi dei suoi elementi sononull
, un NullPointerException
verrà sostituito.
Si noti che l’ordine delle interfacce proxy specificate è significativo: due richieste per una classe proxy con la stessa combinazione di interfacce ma in un ordine diverso risulteranno in due classi proxy distinte. Le classi proxy si distinguono per l’ordine delle loro interfacce proxy al fine di fornire la codifica di invocazione deterministica del metodo nei casi in cui due o più delle interfacce proxyinterfaces condividono un metodo con lo stesso nome e parametersignature; questo ragionamento è descritto in modo più dettagliato nella sezione sotto intitolata Metodi duplicati in più interfacce proxy.
In modo che non sia necessario generare una nuova classe proxy eachtime Proxy.getProxyClass
viene invocato con lo stesso caricatore di classe e l’elenco di interfacce, l’implementazione dell’API della classe proxy dinamica dovrebbe mantenere una cache di proxyclass generate, digitate dai rispettivi caricatori e dall’elenco delle interfacce.L’implementazione dovrebbe fare attenzione a non fare riferimento ai classloader, alle interfacce e alle classi proxy in modo tale da impedire ai caricatori di classe e a tutte le loro classi di essere garbagecollected quando appropriato.
Proprietà della classe proxy
Una classe proxy ha le seguenti proprietà:
- Le classi proxy sono pubbliche, definitive e non astratte.
- Il nome non qualificato di una classe proxy non è specificato. I nomi delle classi spaceof che iniziano con la stringa
"$Proxy"
devono tuttavia essere riservati alle classi proxy. - Una classe proxy estende
java.lang.reflect.Proxy
. - Una classe proxy implementa esattamente le interfacce specificate atits creazione, nello stesso ordine.
- Se una classe proxy implementa un’interfaccia non pubblica, verrà definita nello stesso pacchetto di tale interfaccia. In caso contrario, anche il pacchetto di una classe proxy non è specificato. Si noti che packagesealing non impedirà a una classe proxy di essere definita con successo in un particolare pacchetto in fase di runtime e né willclasses già definite nello stesso caricatore di classi e nello stesso pacchetto con particolari firmatari.
- Dal momento che un proxy classe implementa tutte le interfacce specifiedat sua creazione, invocando
getInterfaces
sulClass
oggetto restituisce un array contenente il samelist di interfacce (nell’ordine specificato al momento della sua creazione),invocandogetMethods
sulClass
objectwill restituire un array diMethod
oggetti che includeall dei metodi in queste interfacce, e invocandogetMethod
trovare metodi proxy di interfacce aswould essere previsto. - Il metodo
Proxy.isProxyClass
restituirà true se viene passata una classe proxy returned una classe restituita daProxy.getProxyClass
o la classe di un oggetto restituita daProxy.newProxyInstance
otherwise e false altrimenti. L’affidabilità di questo metodo è importante per la capacità di usarloper prendere decisioni di sicurezza, quindi la sua implementazione non dovrebbe solo verificare se la classe in questione si estendejava.lang.reflect.Proxy
. - Il
java.security.ProtectionDomain
di un proxyclass è uguale a quello delle classi di sistema caricate dal loader bootstrapclass, ad esempiojava.lang.Object
, perché il codice per una classe proxy è generato da codice di sistema attendibile. Questo dominio di protezione verrà in genere concessojava.security.AllPermission
.
Creazione di un’istanza Proxy
Ogni classe proxy ha un costruttore pubblico che assume oneargument, un’implementazione dell’interfaccia InvocationHandler
.
Ogni istanza proxy ha un oggetto del gestore di invocazione associato, quello che è stato passato al suo costruttore. Piuttosto che dover utilizzare l’API reflection per accedere al costruttore pubblico, è possibile creare anche una proxyinstance chiamando il metodoProxy.newProxyInstance
, che combina le azioni di chiamata Proxy.getProxyClass
con invokingthe constructor con un gestore di invocazione.Proxy.newProxyInstance
generaIllegalArgumentException
per gli stessi motivi cheProxy.getProxyClass
fa.
Proxy di Proprietà di Istanza
proxy istanza ha le seguenti proprietà:
- Data una istanza proxy
proxy
e uno dei theinterfaces implementato dalla sua classe proxyFoo
, i seguenti espressione restituisce true:proxy instanceof Foo
e il cast seguente operazione avrà successo (piuttosto che throwinga
ClassCastException
):(Foo) proxy
- Statico
Proxy.getInvocationHandler
metodo willreturn l’invocazione handler associato con il proxy instancepassed come argomento. Se l’oggetto passato aProxy.getInvocationHandler
non è un’istanza proxy,verrà generato unIllegalArgumentException
. - Un’invocazione del metodo di interfaccia su un’istanza proxy verrà codificata e inviata al metodo
invoke
del gestore di invocazione come descritto di seguito.L’istanza proxy stessa verrà passata come primo argomento di
invoke
, che è di tipoObject
.Il secondo argomento passato a
invoke
sarà l’istanzajava.lang.reflect.Method
corrispondente al metodo di interfaccia invocato sull’istanza proxy. La classe dichiarazionedell’oggettoMethod
sarà l’interfaccia in cui è stato dichiarato themethod, che potrebbe essere una superinterfaccia della proxyinterface attraverso la quale la classe proxy eredita il metodo.Il terzo argomento passato a
invoke
sarà un array di oggetti contenenti i valori degli argomenti passati nell’invocazione del metodo sull’istanza proxy. Gli argomenti di primitivetypes sono racchiusi in un’istanza della classe primitivewrapper appropriata, ad esempiojava.lang.Integer
ojava.lang.Boolean
. L’implementazione del metodoinvoke
è libera di modificare il contenuto di thisarray.Il valore restituito dal metodo
invoke
diventerà il valore restituito dell’invocazione del metodo sull’istanza proxy. Ifthe dichiarato valore di ritorno del metodo di interfaccia è un primitivetype,quindi il valore restituito dainvoke
deve essere aninstance della corrispondente classe wrapper primitivo; altrimenti, deve essere un tipo assegnabile al tipo di ritorno dichiarato. Se il valore restituito dainvoke
ènull
e il tipo di ritorno del metodo theinterface è primitivo, allora unNullPointerException
verrà generato da methodinvocation sull’istanza proxy. Se il valore restituito dainvoke
non è altrimenti compatibile con il tipo di ritorno dichiarato del metodo come descritto sopra, unClassCastException
verrà generato da proxyinstance.Se un’eccezione viene generata dal metodo
invoke
, verrà generata anche dall’invocazione del metodo sull’istanza proxy.Il tipo di eccezione deve essere assegnabile a uno qualsiasi dei tipi di eccezione dichiarati nella firma del metodo di interface o ai tipi di eccezione non controllatijava.lang.RuntimeException
ojava.lang.Error
. Se un’eccezione selezionata viene generata dainvoke
che non è assegnabile a nessuno degli exceptiontypes dichiarati nella clausolathrows
di interfacemethod, allora unUndeclaredThrowableException
verrà generato dall’invocazione del metodo sull’istanza proxy. IlUndeclaredThrowableException
verrà costruito conl’eccezione generata dal metodoinvoke
. - Un’invocazione dei metodi
hashCode
,equals
otoString
dichiarati injava.lang.Object
su un’istanza proxy verrà codificata e inviata al metodoinvoke
del gestore di invocazione nello stesso modo in cui le invocazioni del metodo di interfaccia vengono codificate e inviate, come descritto sopra. La classe dichiarante dell’oggettoMethod
passato ainvoke
saràjava.lang.Object
. Altri metodi pubblici di una proxyinstance ereditata dajava.lang.Object
non sono sovrascritti da una classe proxy, quindi le invocazioni di tali metodi si comportano come fanno per le istanze dijava.lang.Object
.
Metodi duplicati nelle interfacce MultipleProxy
Quando due o più interfacce di una classe proxy contengono un metodo con lo stesso nome e la stessa firma dei parametri, l’ordine delle interfacce di proxyclass diventa significativo. Quando tale duplicatemethod viene invocato su un’istanza proxy, l’oggetto Method
passato al gestore di invocazione non sarà necessariamente quello la cui classe di dichiarazione è assegnabile dal tipo di riferimento dell’interfaccia attraverso cui è stato invocato il metodo del proxy. Thislimitation esiste perché l’implementazione del metodo corrispondente nella classe proxy generata non può determinare quale interfaccia è stata invocata. Pertanto, quando un metodo duplicato viene invocato su un’istanza proxy, l’oggetto Method
per il metodo nell’interfaccia principale che contiene il metodo (direttamente o ereditato tramite una superinterfaccia) nell’elenco ofinterfaces della classe proxy viene passato al metodoinvoke
del gestore di invocazione, indipendentemente dal tipo di riferimento attraverso il quale si è verificata l’invocazione del metodo.
Se un’interfaccia proxy contiene un metodo con lo stesso nome e la firma del parametro dei metodi hashCode
,equals
o toString
dijava.lang.Object
, quando tale metodo viene richiamato su un’istanza proxy, l’oggetto Method
passato al gestore invocation avrà java.lang.Object
come classe itsdeclaring. In altre parole, i metodi pubblici e non finali dijava.lang.Object
precedono logicamente tutte le proxyinterfaces per la determinazione di quale oggetto Method
passare al gestore di invocazione.
Si noti inoltre che quando un metodo duplicato viene inviato a un gestore di invocazione, il metodo invoke
può solo throwchecked tipi di eccezione che sono assegnabili a uno dei exceptiontypes nella clausola throws
del metodo in allof le interfacce proxy che può essere richiamato attraverso. Se il metodo invoke
genera un’eccezione selezionata che non è assegnabile a nessuno dei tipi di eccezione dichiarati dal metodo inuna delle interfacce proxy attraverso cui può essere invocata, anunchecked UndeclaredThrowableException
verrà generato dall’invocazione sull’istanza proxy. Questa restrizione significa che non tutti i tipi di eccezione restituiti invocandogetExceptionTypes
sul Method
objectpassed al invoke
metodo può necessariamente essere thrownsuccessfully dal invoke
metodo.
Serializzazione
Poichéjava.lang.reflect.Proxy
implementa java.io.Serializable
, le istanze proxy possono essere serializzate, come descritto in questa sezione. Se un’istanza proxy contiene un gestore di invocazione che non è assegnabile ajava.io.Serializable
, tuttavia, verrà generato unjava.io.NotSerializableException
se tale istanza viene scritta su unjava.io.ObjectOutputStream
. Si noti che per proxyclasses, l’implementazione di java.io.Externalizable
ha lo stesso effetto rispetto alla serializzazione dell’implementazione dijava.io.Serializable
: i metodi writeExternal
e readExternal
dell’interfacciaExternalizable
non verranno mai richiamati su un’istanza aproxy (o un gestore di invocazione) come parte del processo di serializzazione. Come per tutti gli oggetti Class
, l’oggetto Class
per una classe proxy è alwaysserializable.
Una classe proxy non ha campi serializzabili e unserialVersionUID
di 0L
. In altre parole, quando l’oggetto Class
per una classe proxy viene passato al metodo statico lookup
dijava.io.ObjectStreamClass
, l’istanzaObjectStreamClass
restituita avrà le seguenti proprietà:
- Richiamando il suo metodo
getSerialVersionUID
willreturn0L
. - Richiamando il suo metodo
getFields
restituirà un array di lunghezza zero. - Richiamando il suo metodo
getField
con qualsiasi argomentoString
restituirànull
.
Il protocollo stream per la serializzazione degli oggetti supporta un typecode denominato TC_PROXYCLASSDESC
, che è un terminalsymbol nella grammatica per il formato stream; il tipo e il valore aredefined dal seguente campo costante neljava.io.ObjectStreamConstants
interfaccia:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
La grammatica comprende anche le seguenti due regole, la firstbeing un supplente di espansione dell’originale newClassDescrule:
newClassDesc:TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
proxyInterfaceName classAnnotationsuperClassDesc
proxyInterfaceName:(utf)
Quando un ObjectOutputStream
serializza classdescriptor per una classe che è una classe proxy, come determinato bypassando il suo oggetto Class
al metodoProxy.isProxyClass
, utilizza il codice di tipoTC_PROXYCLASSDESC
invece diTC_CLASSDESC
, seguendo le regole di cui sopra. Nell’espansione di proxyClassDescInfo, la sequenza di elementi Proxyinterfacename sono i nomi di tutte le interfacce implementate dalla classe proxy, nell’ordine in cui vengono restituite richiamando il metodo getInterfaces
sull’oggetto Class
. Gli elementi classAnnotation andsuperClassDesc hanno lo stesso significato della regola Classdescinfo. Per una classe proxy, superClassDescis il descrittore di classe per la sua superclasse,java.lang.reflect.Proxy
; incluso questo descriptorallows per l’evoluzione della rappresentazione serializzata di theclass Proxy
per le istanze proxy.
Per le classi non proxy, ObjectOutputStream
chiama itsprotected annotateClass
metodo per consentire sottoclassi towrite dati personalizzati al flusso per una particolare classe. Per proxyclasses, invece di annotateClass
, il seguente metodo in java.io.ObjectOutputStream
viene chiamato con l’oggetto Class
per la classe proxy:
protected void annotateProxyClass(Class cl) throws IOException;
L’implementazione predefinita di annotateProxyClass
inObjectOutputStream
non fa nulla.
Quando un ObjectInputStream
incontra il codice di tipoTC_PROXYCLASSDESC
, deserializza classdescriptor per una classe proxy dal flusso, formattato come descritto sopra. Invece di chiamare il suo metodo resolveClass
per risolvere l’oggetto Class
per classdescriptor, viene chiamato il seguente metodo injava.io.ObjectInputStream
:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
L’elenco dei nomi di interfaccia deserializzati nel descrittore proxyclass viene passato come interfaces
argumentto resolveProxyClass
.
L’implementazione predefinita di resolveProxyClass
inObjectInputStream
restituisce i risultati della chiamataProxy.getProxyClass
con l’elenco diClass
oggetti per le interfacce nominate nel parametrointerfaces
. L’oggetto Class
utilizzato per ogni nome di interfaccia i
è il valore risintonizzato da calling
Class.forName(i, false, loader)
doveloader
è il primo caricatore di classe non null nello stack di esecuzione, onull
se non sono presenti caricatori di classe non null nello stack. Questa è la stessa scelta del caricatore di classe fatta dal comportamento predefinito del metodoresolveClass
. Questo stesso valore diloader
è anche il caricatore di classe passato aProxy.getProxyClass
. SeProxy.getProxyClass
genera unIllegalArgumentException
,resolveClass
genererà unClassNotFoundException
contenenteIllegalArgumentException
.
Poiché una classe proxy non ha mai i propri campi serializzabili, ilclassdata nella rappresentazione del flusso di un’istanza proxy consiste interamente dei dati dell’istanza per la sua superclasse,java.lang.reflect.Proxy
. Proxy
ha un campo oneserializable, h
, che contiene invocationhandler per l’istanza proxy.
Esempi
Ecco un semplice esempio che stampa un messaggio prima e dopo l’invocazione di un metodo su un oggetto che implementa un elenco arbitrario di interfacce:
public interface Foo { Object bar(Object obj) throws BazException;}public class FooImpl implements Foo { Object bar(Object obj) throws BazException { // ... }}public class DebugProxy implements java.lang.reflect.InvocationHandler { private Object obj; public static Object newInstance(Object obj) { return java.lang.reflect.Proxy.newProxyInstance( obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new DebugProxy(obj)); } private DebugProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Object result; try { System.out.println("before method " + m.getName()); result = m.invoke(obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (Exception e) { throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); } finally { System.out.println("after method " + m.getName()); } return result; }}
Per costruire un DebugProxy
per un’implementazione dell’interfaccia Foo
e chiamare uno dei suoi metodi:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
Ecco un esempio di una classe di gestore di invocazioni di utilità che fornisce un comportamento proxy predefinito per i metodi ereditati dajava.lang.Object
e implementa la delega di determinate invocazioni del metodoproxy a oggetti distinti a seconda dell’interfaccia del metodo richiamato:
import java.lang.reflect.*;public class Delegator implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode", null); equalsMethod = Object.class.getMethod("equals", new Class { Object.class }); toStringMethod = Object.class.getMethod("toString", null); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private Class interfaces; private Object delegates; public Delegator(Class interfaces, Object delegates) { this.interfaces = (Class) interfaces.clone(); this.delegates = (Object) delegates.clone(); } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Class declaringClass = m.getDeclaringClass(); if (declaringClass == Object.class) { if (m.equals(hashCodeMethod)) { return proxyHashCode(proxy); } else if (m.equals(equalsMethod)) { return proxyEquals(proxy, args); } else if (m.equals(toStringMethod)) { return proxyToString(proxy); } else { throw new InternalError( "unexpected Object method dispatched: " + m); } } else { for (int i = 0; i < interfaces.length; i++) { if (declaringClass.isAssignableFrom(interfaces)) { try { return m.invoke(delegates, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } return invokeNotDelegated(proxy, m, args); } } protected Object invokeNotDelegated(Object proxy, Method m, Object args) throws Throwable { throw new InternalError("unexpected method dispatched: " + m); } protected Integer proxyHashCode(Object proxy) { return new Integer(System.identityHashCode(proxy)); } protected Boolean proxyEquals(Object proxy, Object other) { return (proxy == other ? Boolean.TRUE : Boolean.FALSE); } protected String proxyToString(Object proxy) { return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); }}
Le sottoclassi diDelegator
possono sovrascrivere invokeNotDelegated
per implementare il comportamento delle invocazioni proxymethod da non delegare direttamente ad altri oggetti e possono sovrascrivereproxyHashCode
, proxyEquals
e proxyToString
per aggirare il comportamento predefinito dei metodi ereditati dal proxy da java.lang.Object
.
Per costruire un Delegator
per un’implementazione dell’interfaccia Foo
:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
Si noti che l’implementazione di Delegator
classgiven sopra è destinata ad essere più illustrativa di quella ottimizzata; ad esempio, invece di memorizzare nella cache e confrontare gli oggetti Method
per i metodi hashCode
, equals
etoString
, potrebbe semplicemente abbinarli con i nomi delle stringhe, perché nessuno di questi nomi di metodi è sovraccarico injava.lang.Object
.