Dinamica Classi Proxy

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 Objectcontenente 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 identici Class.
  • Tutti i tipi di interfaccia devono essere visibili per nome tramite il caricatore di classi specificato. In altre parole, per class loadercl e ogni interfaccia i, 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.getProxyClassgenererà 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),invocando getMethods sul Class objectwill restituire un array di Method 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 da Proxy.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 esempio java.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 proxy Foo, 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 un IllegalArgumentException.
  • Un’invocazione del metodo di interfaccia su un’istanza proxy verrà codificata e inviata al metodoinvoke del gestore di invocazione come descritto di seguito.

    L’istanza proxy stessa verrà passata come primo argomento di invoke, che è di tipo Object.

    Il secondo argomento passato a invoke sarà l’istanzajava.lang.reflect.Method corrispondente al metodo di interfaccia invocato sull’istanza proxy. La classe dichiarazionedell’oggetto Method 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 esempio java.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 da invoke deve essere aninstance della corrispondente classe wrapper primitivo; altrimenti, deve essere un tipo assegnabile al tipo di ritorno dichiarato. Se il valore restituito da invoke è 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 da invoke che non è assegnabile a nessuno degli exceptiontypes dichiarati nella clausola throws di interfacemethod, allora un UndeclaredThrowableExceptionverrà generato dall’invocazione del metodo sull’istanza proxy. IlUndeclaredThrowableExceptionverrà costruito conl’eccezione generata dal metodo invoke.

  • Un’invocazione dei metodihashCode, equals otoString dichiarati in java.lang.Object su un’istanza proxy verrà codificata e inviata al metodo invokedel 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’oggetto Method passato a invoke sarà java.lang.Object. Altri metodi pubblici di una proxyinstance ereditata da java.lang.Object non sono sovrascritti da una classe proxy, quindi le invocazioni di tali metodi si comportano come fanno per le istanze di java.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 Methodpassato 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 writeExternale 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 unserialVersionUIDdi 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 willreturn 0L.
  • Richiamando il suo metodo getFields restituirà un array di lunghezza zero.
  • Richiamando il suo metodogetField con qualsiasi argomento String 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_PROXYCLASSDESCnewHandle 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, onullse 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.getProxyClassgenera unIllegalArgumentException,resolveClassgenererà unClassNotFoundExceptioncontenenteIllegalArgumentException.

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 invokeNotDelegatedper implementare il comportamento delle invocazioni proxymethod da non delegare direttamente ad altri oggetti e possono sovrascrivereproxyHashCode, proxyEquals e proxyToStringper 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 Methodper i metodi hashCode, equals etoString, potrebbe semplicemente abbinarli con i nomi delle stringhe, perché nessuno di questi nomi di metodi è sovraccarico injava.lang.Object.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.