cuprins
Introducere
API Proxy dinamic
serializare
Exemple
Introducere
o clasă proxy dinamică este o clasă care implementează o listă de interfețe specificate în timpul rulării, astfel încât o metodă invocare printr-una dintre interfețele de pe o instanță a clasei va fi codificată și expediată către un alt obiect printr-o interfață uniformă. Astfel, o clasă proxy dinamică poate fi utilizată pentru a crea un obiect proxy atype-safe pentru o listă de interfețe fără a necesita generarea prealabilă a clasei proxy, cum ar fi instrumentele de compilare.Invocările metodei pe o instanță a unei clase proxy dinamice sunt expediate către o singură metodă în invocationhandler-ul instanței și sunt codificate cu un obiectjava.lang.reflect.Method
care identifică metoda care a fost invocată și o matrice de tip Object
care conține argumentele.
clasele proxy dinamice sunt utile pentru o aplicație sau o bibliotecă care trebuie să furnizeze expedierea reflectorizantă în condiții de siguranță a invocațiilorpe obiecte care prezintă API-uri de interfață. De exemplu, o applicationcan utiliza o clasă proxy dinamic pentru a crea un obiect care implementsmultiple arbitrare event listener interfaces– interfaces thatextend java.util.EventListener
— pentru a procesa o varietate de evenimente de diferite tipuri într-un mod uniform, cum ar fi bylogging toate aceste evenimente într-un fișier.
Dynamic Proxy Class API
o clasă proxy dinamică (denumită pur și simplu o clasă proxyclass de mai jos) este o clasă care implementează o listă de interfațespecificate în timpul rulării când este creată clasa.
o interfață proxy este o astfel de interfață care esteimplementată de o clasă proxy.
o instanță proxy este o instanță a unei clase proxyclass.
crearea unei clase Proxy
clasele Proxy, precum și instanțele acestora, sunt create folosindmetodele statice ale clasei java.lang.reflectă.Proxy.
metodaProxy.getProxyClass
returnează obiectul java.lang.Class
pentru o clasă proxy dat un classloader și o serie de interfețe. Clasa proxy va fi definităîn încărcătorul de clasă specificat și va implementa toate interfețele furnizate. Dacă o clasă proxy pentru aceeași permutare a interfețelor a fost deja definită în încărcătorul clasei, atunci clasa proxy existentă va fi returnată; în caz contrar, o clasă proxy pentru aceste interfețe va fi generată dinamic și definită în încărcătorul clasei.
există mai multe restricții privind parametrii care pot fitrecut la Proxy.getProxyClass
:
- toate obiectele
Class
din matriceainterfaces
trebuie să reprezinte interfețe, notclasses sau tipuri primitive. - nu există două elemente în matricea
interfaces
se pot referi la obiecte identiceClass
. - toate tipurile de interfață trebuie să fie vizibile după nume prin încărcătorul de clasă specificat. Cu alte cuvinte, pentru încărcătorul de clasă
cl
și fiecare interfațăi
, următoarea expresie trebuie să fie adevărată:Class.forName(i.getName(), false, cl) == i
- toate interfețele non-publice trebuie să fie în același pachet;în caz contrar, nu ar fi posibil ca clasa proxy să implementeze toate interfețele, indiferent de pachetul în care este definit.
- pentru orice set de metode membre ale interfețelor specificate careau aceeași semnătură:
- dacă tipul de returnare al oricăreia dintre metode este un tip primitiv orvoid, atunci toate metodele trebuie să aibă același tip de returnare.
- în caz contrar, una dintre metode trebuie să aibă un tip de returnare care să poată fi atribuit tuturor tipurilor de returnare ale celorlalte metode.
- clasa proxy rezultată nu trebuie să depășească limitele impuseclase de către mașina virtuală. De exemplu, VM poate limita numărul de interfețe pe care o clasă le poate implementa la 65535; în acest caz, dimensiunea matricei
interfaces
nu trebuie să depășească 65535.
dacă oricare dintre aceste restricții sunt încălcate,Proxy.getProxyClass
va arunca un IllegalArgumentException
. Dacă argumentul matriceiinterfaces
sau oricare dintre elementele sale sunt null
, va fi un NullPointerException
.
rețineți că ordinea interfețelor proxy specificate este semnificativă: două solicitări pentru o clasă proxy cu aceeași combinație de interfețe, dar într-o ordine diferită, vor rezulta în două clase proxy distincte. Clasele Proxy se disting prin theorder interfețelor lor proxy pentru a furniza codificarea invocării metodei deterministe în cazurile în care două sau mai multe dintre interfețele proxy împărtășesc o metodă cu același nume și parametrisemnatura; acest raționament este descris mai detaliat însecțiunea de mai jos intitulată metode duplicate înmultiple interfețe Proxy.
astfel încât o nouă clasă proxy nu trebuie să fie generate eachtime Proxy.getProxyClass
este invocat cu încărcător sameclass și lista de interfețe, punerea în aplicare a THEDYNAMIC proxy class API ar trebui să păstreze o memorie cache de proxyclasses generate, tastate de incarcator lor corespunzătoare și lista de interfață.Punerea în aplicare ar trebui să fie atent să nu se refere la classloaders, interfețe, și clase proxy în așa fel încât să împiedice Încărcătoare de clasă, și toate clasele lor, de la a fi gunoierecolected atunci când este cazul.
Proprietăți de clasă Proxy
o clasă proxy are următoarele proprietăți:
- clasele Proxy sunt publice, finale și nu abstracte.
- numele necalificat al unei clase proxy este nespecificat. Spaceof nume de clase care încep cu șirul
"$Proxy"
este,cu toate acestea, să fie rezervat pentru clasele proxy. - o clasă proxy se extinde
java.lang.reflect.Proxy
. - o clasă proxy implementează exact interfețele specificate lacrearea sa, în aceeași ordine.
- dacă o clasă proxy implementează o interfață non-publică, atunci aceasta va fi definită în același pachet ca acea interfață. În caz contrar, pachetul unei clase proxy este, de asemenea, nespecificat. Rețineți că packagesealing nu va împiedica o clasă proxy să fie definită cu succes într-un anumit pachet în timpul rulării și nici clasele deja definite în același încărcător de clasă și același pachet cu anumiți semnatari.
- deoarece o clasă proxy implementează toate interfețele specificate la crearea sa, invocând
getInterfaces
pe obiectul săuClass
va returna o matrice care conține aceeași listă de interfețe (în ordinea specificată la crearea sa),invocândgetMethods
pe obiectul săuClass
va returna o matrice de obiecteMethod
care includ toate metodele din acele interfețe și invocândgetMethod
vor găsi metode în interfețele proxy fi de așteptat. - metoda
Proxy.isProxyClass
va returna true dacă este trecută o clasă proxy-o clasă returnată deProxy.getProxyClass
sau clasa unui obiect returnedbyProxy.newProxyInstance
– și false altfel. Fiabilitatea acestei metode este importantă pentru capacitatea de a o utilizapentru a lua decizii de securitate, astfel încât punerea sa în aplicare nu ar trebui să fie doartest dacă clasa în cauză se extindejava.lang.reflect.Proxy
. -
java.security.ProtectionDomain
a unei clase proxyclass este aceeași cu cea a claselor de sistem încărcate de încărcătorul bootstrapclass, cum ar fijava.lang.Object
, deoarece codul pentru o clasă proxy este generat de codul de sistem de încredere. Acest domeniu de protecție va fi de obicei acordatjava.security.AllPermission
.
crearea unei instanțe Proxy
fiecare clasă proxy are un constructor public care preia oneargument, o implementare a interfeței InvocationHandler
.
fiecare instanță proxy are un obiect handler de invocare asociat,cel care a fost transmis constructorului său. În loc să utilizeze API-ul de reflecție pentru a accesa constructorul public, un proxy poate fi creat și apelând metodaProxy.newProxyInstance
, care combină acțiunile de apelare Proxy.getProxyClass
cu invocarea constructorului cu un handler de invocare.Proxy.newProxyInstance
aruncăIllegalArgumentException
din aceleași motive ca șiProxy.getProxyClass
.
proprietăți instanță Proxy
o instanță proxy are următoarele proprietăți:
- având în vedere o instanță proxy
proxy
și una dintre interfețele implementate de clasa proxyFoo
, următoarea expresie va reveni true:proxy instanceof Foo
și următoarea operație cast va reuși (mai degrabă decât throwinga
ClassCastException
):(Foo) proxy
- static
Proxy.getInvocationHandler
metoda willreturn handler invocare asociate cu instancepassed proxy ca argumentul său. Dacă obiectul trecut laProxy.getInvocationHandler
nu este o instanță proxy,atunci va fi aruncat unIllegalArgumentException
. - o invocare a metodei de interfață pe o instanță proxy va fi codificată și expediată la metoda
invoke
a handlerului de invocare, așa cum este descris mai jos.instanța proxy în sine va fi trecut ca primul argumentof
invoke
, care este de tipObject
.al doilea argument transmis la
invoke
va fi instanțajava.lang.reflect.Method
corespunzătoare metodei interface invocate pe instanța proxy. Clasa de declarare a obiectuluiMethod
va fi interfața în care a fost declarată themethod, care poate fi o superinterfață a proxyinterface prin care clasa proxy moștenește metoda.al treilea argument transmis la
invoke
va fi anarray de obiecte care conțin valorile argumentelor transmise în invocarea metodei pe instanța proxy. Argumentele primitivetypes sunt înfășurate într-o instanță a clasei primitivewrapper corespunzătoare, cum ar fijava.lang.Integer
saujava.lang.Boolean
. Punerea în aplicare a metodeiinvoke
este liber de a modifica conținutul thisarray.valoarea returnată de metoda
invoke
va devinevaloarea returnată a invocării metodei pe instanța proxy. Dacăvaloarea returnată declarată a metodei de interfață este un tip primitiv, atunci valoarea returnată deinvoke
trebuie să fie oinstanță a clasei de înveliș primitiv corespunzătoare; în caz contrar,trebuie să fie un tip care poate fi atribuit tipului de returnare declarat. Dacă valoarea returnată deinvoke
estenull
și tipul de returnare al metodei Interface este primitiv, atunci oNullPointerException
va fi aruncată de methodinvocation pe instanța proxy. Dacă valoarea returnată deinvoke
nu este compatibilă cu tipul de returnare declarat al metodei, așa cum este descris mai sus, unClassCastException
va fi aruncat de către proxyinstance.dacă o excepție este aruncată prin metoda
invoke
, aceasta va fi aruncată și prin invocarea metodei pe instanța proxy.Tipul excepției trebuie să poată fi atribuit oricăruia dintre tipurile de excepție declarate în semnătura metodei interfeței sau tipurilor de excepție necontrolatejava.lang.RuntimeException
saujava.lang.Error
. Dacă o excepție bifată este aruncată deinvoke
care nu poate fi atribuită niciunui tip de excepție declarat în clauzathrows
a interfacemethod, atunci unUndeclaredThrowableException
va fi aruncat prin metoda invocare pe instanța proxy.UndeclaredThrowableException
va fi construit cu excepția care a fost aruncată prin metodainvoke
. - o invocare a metodelor
hashCode
,equals
sautoString
declarate înjava.lang.Object
pe o instanță proxy va fi codificată și expediată la metodainvoke
a handler-ului de invocare în același mod ca și invocările metodei de interfață sunt codificate și expediate, așa cum este descris mai sus. Clasa de declarare a obiectuluiMethod
trecut lainvoke
va fijava.lang.Object
. Alte metode publice ale unei proxyinstance moștenite de lajava.lang.Object
nu sunt suprascrise de o clasă proxy, astfel încât invocările acestor metode se comportă ca acestea pentru instanțe dejava.lang.Object
.
metode duplicate în Multipleproxy Interfaces
când două sau mai multe interfețe ale unei clase proxy conțin o metodă cu același nume și semnătura parametrului, ordinea interfețelor proxyclass devine semnificativă. Atunci când o astfel de metodă duplicatemethod este invocată pe o instanță proxy, obiectul Method
transmis manipulatorului de invocare nu va fi neapărat cel a cărui clasă de declarare este atribuibilă din tipul de referință al interfeței prin care a fost invocată metoda proxy-ului. Thislimitation există deoarece metoda corespunzătoare implementationin clasa proxy generat nu poate determina care interfață itwas invocat prin. Prin urmare, atunci când o metodă duplicat este invocat pe o instanță proxy, Method
obiect pentru methodin interfața rând care conține metoda (fie directly sau moștenit printr-o superinterface) în lista de interfaces clasa proxy este trecut la handler invocareinvoke
metoda, indiferent de tipul de referințăprin care a avut loc invocarea metodei.
dacă o interfață proxy conține o metodă cu același nume și semnătură parametrică ca metodele hashCode
,equals
sau toString
alejava.lang.Object
, atunci când o astfel de metodă este invocată pe o instanță apropiată, obiectul Method
trecut la handlerul de localizare va avea java.lang.Object
ca clasă de declarare. Cu alte cuvinte, metodele publice, non-finale alejava.lang.Object
preced în mod logic toate interfețele proxy pentru determinarea cărora Method
obiect să treacă la handler-ul de invocare.
rețineți, de asemenea, că atunci când o metodă duplicat este expediat la handler aninvocation, invoke
metoda poate throwchecked numai tipuri de excepție, care sunt atribuite la unul dintre exceptiontypes în throws
clauza metodei în allof interfețele proxy care poate fi invocată prin. Dacă metoda invoke
aruncă o excepție bifată care nu poate fi atribuită niciunuia dintre tipurile de excepții declarate de metodă în una dintre interfețele proxy prin care poate fi invocată, atunci UndeclaredThrowableException
bifat va fi aruncat de invocarea instanței proxy. Această restricție înseamnă că nu toate tipurile de excepții returnate prin invocareagetExceptionTypes
pe obiectul Method
trecut la metoda invoke
pot fi neapărat aruncate cu succes prin metoda invoke
.
serializare
deoarecejava.lang.reflect.Proxy
implementează java.io.Serializable
, instanțele proxy pot fi serializate, așa cum este descris în această secțiune. Dacă un proxy instanceconține un handler de invocare care nu poate fi atribuitjava.io.Serializable
, totuși, atunci unjava.io.NotSerializableException
va fi aruncat dacă o astfel de instanță este scrisă unuijava.io.ObjectOutputStream
. Rețineți că pentru proxyclasses, implementarea java.io.Externalizable
are același efect în ceea ce privește serializarea ca implementareajava.io.Serializable
: metodele writeExternal
și readExternal
ale interfețeiExternalizable
nu vor fi niciodată invocate pe o instanță apropiată (sau un handler de invocare) ca parte a procesului său de serializare. Ca și în cazul tuturor obiectelor Class
, obiectulClass
pentru o clasă proxy este întotdeaunaserializabil.
o clasă proxy nu are câmpuri serializabile și unserialVersionUID
de 0L
. Cu alte cuvinte, atunci când obiectul Class
pentru o clasă proxy este trecut la metoda statică lookup
a java.io.ObjectStreamClass
, instanța returnată ObjectStreamClass
va avea următoarele proprietăți:
- invocarea metodei sale
getSerialVersionUID
va reveni0L
. - invocarea metodei sale
getFields
va returna o lungime arrayof zero. - invocarea metodei sale
getField
cu orice argumentString
va reveninull
.
protocolul stream pentru serializarea obiectelor acceptă un cod de tip numit TC_PROXYCLASSDESC
, care este un terminalsymbol în gramatica formatului stream; tipul și valoarea sa sunt definite de următorul câmp constant în interfațajava.io.ObjectStreamConstants
:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
gramatica include, de asemenea, următoarele două reguli, primulfiind o extindere alternativă a originalului newClassDescrule:
newClassDesc:TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
proxyinterfacename classAnnotationsuperClassDesc
proxyinterfacename:(utf)
când un ObjectOutputStream
serializează classdescriptor pentru o clasă care este o clasă proxy, așa cum este determinat ocolind obiectul său Class
la metodaProxy.isProxyClass
, folosește codul de tipTC_PROXYCLASSDESC
în loc deTC_CLASSDESC
, urmând regulile de mai sus. Înexpansiunea proxyClassDescInfo, secvența de elemente de nume proxyinterfacename sunt numele tuturor interfețelor implementate de clasa proxy, în ordinea în care acestea sunt returnate prin invocarea metodei getInterfaces
pe obiectul Class
. Elementele classAnnotation anduperclassdesc au același înțeles ca și în regula classdescinfo. Pentru o clasă proxy, superClassDescis descriptorul clasei pentru superclasa sa,java.lang.reflect.Proxy
; inclusiv acest descriptorallows pentru evoluția reprezentării serializate a clasei Proxy
pentru instanțele proxy.
pentru clasele non-proxy, ObjectOutputStream
apelează metoda itsprotected annotateClass
pentru a permite subclaselor să scrie date personalizate în flux pentru o anumită clasă. Pentru proxiclase, în loc de annotateClass
, metoda următoare în java.io.ObjectOutputStream
se numește cu Class
obiect pentru clasa proxy:
protected void annotateProxyClass(Class cl) throws IOException;
implementarea implicită a annotateProxyClass
înObjectOutputStream
nu face nimic.
când un ObjectInputStream
întâlnește codul de tipTC_PROXYCLASSDESC
, acesta deserializează classdescriptor pentru o clasă proxy din flux, formatat așa cum este descris mai sus. În loc de asteptare sale resolveClass
metoda pentru a rezolva Class
obiect pentru classdescriptor, următoarea metodă înjava.io.ObjectInputStream
se numește:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
lista de nume de interfață care au fost deserializate în descriptor proxyclass sunt trecute ca interfaces
argumentla resolveProxyClass
.
implementarea implicită aresolveProxyClass
înObjectInputStream
returnează rezultatele apelăriiProxy.getProxyClass
cu lista de obiecteClass
pentru interfețele denumite în parametrul interfaces
. Obiectul Class
utilizat pentru fiecare nume de interfațăi
este valoarea reglată prin apelarea
Class.forName(i, false, loader)
undeloader
este primul încărcător de clasă non-null din stiva de execuție saunull
dacă nu există încărcătoare de clasă non-null pe stivă. Aceasta este aceeași alegere a încărcătorului de clasă făcută decomportamentul implicit al metodeiresolveClass
. Această valoare aloader
este, de asemenea, încărcătorul de clasă trecut laProxy.getProxyClass
. DacăProxy.getProxyClass
aruncă unIllegalArgumentException
,resolveClass
va arunca unClassNotFoundException
care conțineIllegalArgumentException
.
deoarece o clasă proxy nu are niciodată propriile câmpuri serializabile, datele de clasă din reprezentarea în flux a unei instanțe proxy constau în întregime din datele instanței pentru superclasa sa,java.lang.reflect.Proxy
. Proxy
are un câmp serializabil, h
, care conține invocationhandler pentru instanța proxy.
Exemple
Iată un exemplu simplu care imprimă un mesaj înainte și după o invocare a metodei pe un obiect care implementează o listă arbitrară de interfețe:
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; }}
pentru a construi o DebugProxy
pentru o implementare a interfeței Foo
și a apela una dintre metodele sale:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
aici este un exemplu de o clasă de utilitate invocare handler thatprovides comportament proxy implicit pentru metodele moștenite de lajava.lang.Object
și implementează delegarea certainproxy metoda invocări la obiecte distincte în funcție de theinterface a metodei invocate:
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()); }}
subclasele Delegator
pot suprascrieinvokeNotDelegated
pentru a implementa comportamentul invocărilor proxymethod care nu trebuie delegate direct altor obiecte și pot suprascrie proxyHashCode
,proxyEquals
și proxyToString
pentru a suprascrie comportamentul implicit al metodelor moștenite de proxy java.lang.Object
.
pentru a construi o Delegator
pentru o implementare a interfeței Foo
:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
rețineți că punerea în aplicare a Delegator
classgiven de mai sus este destinat să fie mai ilustrativ decât optimizat; de exemplu, în loc să memoreze în cache și să compare obiectele Method
pentru metodele hashCode
, equals
șitoString
, le-ar putea potrivi doar după numele lor de șir, deoarece niciunul dintre aceste nume de metode nu este supraîncărcat înjava.lang.Object
.