Innhold
Introduksjon
Dynamic Proxy API
Serialisering
Eksempler
Introduksjon
en dynamisk proxy-klasse er en klasse som implementerer en liste over grensesnitt som er angitt under kjøring, slik at en metode påkalling gjennom et av grensesnittene på en forekomst av klassen vil bli kodet og sendt til et annet objekt gjennom et uniforminterface. Dermed kan en dynamisk proxy-klasse brukes til å lage atype-sikker proxy-objekt for en liste over grensesnitt uten å kreve forhåndsgenerering av proxy-klassen, for eksempel med kompileringstidsverktøy.Metodekallinger på en forekomst av en dynamisk proxy-klasse er sendt til en enkelt metode i forekomstens invocationhandler, og de er kodet med etjava.lang.reflect.Method
– objekt som identifiserer metodendet ble påkalt og en matrise av typen Object
som inneholder argumentene.
Dynamiske proxy-klasser er nyttige for et program eller bibliotek, som må gi typesikker reflekterende forsendelse av invocationson-objekter som presenterer grensesnitt-Apier. Et program kan for eksempel bruke en dynamisk proxy-klasse til å opprette et objekt som implementerermultiple vilkårlig hendelseslyttegrensesnitt-grensesnitt som utvider java.util.EventListener
– for å behandle en rekke hendelser av forskjellige typer på en ensartet måte, for eksempel ved å logge alle slike hendelser til en fil.
Dynamic Proxy Class API
en dynamisk proxy klasse (bare referert til som en proxyclass nedenfor) er en klasse som implementerer en liste over grensesnittspesifisert under kjøring når klassen er opprettet.
et proxy-grensesnitt er et slikt grensesnitt som erimplementert av en proxy-klasse.
en proxy-forekomst er en forekomst av en proxyclass.
Opprette En Proxy Klasse
Proxy klasser, samt forekomster av dem, opprettes ved hjelp avde statiske metodene til klassen java.lang.reflektere.Fullmakt.
metoden Proxy.getProxyClass
returnerer objektet java.lang.Class
for en proxy-klasse gitt en classloader og en rekke grensesnitt. Proxy-klassen vil bli definerti den angitte klasselasteren og vil implementere alle de medfølgende grensesnittene. Hvis en proxy-klasse for samme permutasjon av grensesnitt allerede er definert i klasselasteren, vil den eksisterende proxy-klassen bli returnert; ellers vil en proxy-klasse forde grensesnittene bli generert dynamisk og definert i klasse loader.
det er flere begrensninger på parametrene som kan værepasseres til Proxy.getProxyClass
:
- alle
Class
– objektene iinterfaces
– matrisen må representere grensesnitt, ikke klasser eller primitive typer. - ingen to elementer i
interfaces
– matrisen kan referere til identiskeClass
objekter. - Alle grensesnittypene må være synlige med navn gjennom thespecified class loader. Med andre ord, for klasselaster
cl
og hvert grensesnitti
, må følgendeuttrykket være sant:Class.forName(i.getName(), false, cl) == i
- Alle ikke-offentlige grensesnitt må være i samme pakke; ellers ville det ikke være mulig for proxy-klassen å implementere alle grensesnittene, uavhengig av hvilken pakke det er definert i.
- for et sett med medlemsmetoder for de angitte grensesnittene somhar samme signatur:
- hvis returtypen til noen av metodene er en primitiv type orvoid, må alle metodene ha samme returtype.
- ellers må en av metodene ha en returtype som kan tilordnes alle returtyper for resten av themethods.
- den resulterende proxy-klassen må ikke overskride noen grenser pålagt klasser av den virtuelle maskinen. VM kan for eksempel begrense antall grensesnitt som en klasse kan implementere til 65535; i dette tilfellet må størrelsen på
interfaces
– matrisen ikke overskride 65535.
hvis noen av disse restriksjonene brytes, vilProxy.getProxyClass
kaste enIllegalArgumentException
. Hvis matriseargumentetinterfaces
eller noen av dens elementer ernull
, vil en NullPointerException
bli brutt.
Merk at rekkefølgen på de angitte proxy-grensesnittene er betydelig: to forespørsler om en proxy-klasse med samekombinasjon av grensesnitt, men i en annen rekkefølge, vil resultere i to forskjellige proxy-klasser. Proxy-klasser er preget av teorder av deres proxy-grensesnitt for å gi deterministicmethod invocation-koding i tilfeller der to eller flere av proxyinterfaces deler en metode med samme navn og parametersignatur; denne resonnementet er beskrevet mer detaljert i avsnittet Nedenfor med tittelen Metoder Duplisert inMultiple Proxy-Grensesnitt.
slik at en ny proxy-klasse ikke trenger å bli generert hver gang Proxy.getProxyClass
påberopes med sameclass loader og liste over grensesnitt, bør implementeringen avdynamic proxy class API holde en cache av genererte proxyclasses, tastet av deres tilsvarende lastere og grensesnittliste.Implementeringen bør være forsiktig så du ikke refererer til klasselastere, grensesnitt og proxy-klasser på en slik måte at klasselastere, og alle deres klasser, ikke blir søppeloppsamlet når det passer.
Egenskaper For Proxy-Klasse
en proxy-klasse har følgende egenskaper:
- Proxy klasser er offentlige, endelige og ikke abstrakte.
- det ukvalifiserte navnet på en proxy-klasse er uspesifisert. Områdenav klassenavnene som begynner med strengen
"$Proxy"
, skal imidlertid reserveres for proxy-klasser. - en proxy-klasse utvides
java.lang.reflect.Proxy
. - en proxy-klasse implementerer nøyaktig grensesnittene angitt atits opprettelse, i samme rekkefølge.
- hvis en proxy-klasse implementerer et ikke-offentlig grensesnitt, vil det bli definert i samme pakke som det grensesnittet. Ellers er pakken av en proxy-klasse også uspesifisert. Merk at packagesealing ikke vil hindre at en proxy-klasse blir successfullydefined i en bestemt pakke under kjøring, og heller ikke willclasses som allerede er definert i samme klasselaster og samepackage med bestemte underskrivere.
- siden en proxy-klasse implementerer alle grensesnittene som er spesifisertved opprettelsen, vil påkalling
getInterfaces
på sittClass
– objekt returnere en matrise som inneholder samelisten over grensesnitt (i den rekkefølgen som er angitt ved opprettelsen),påkallegetMethods
på sittClass
– objektvil returnere en matrise medMethod
objekter som inkludereralle metodene i disse grensesnittene, og påkallegetMethod
vil finne metoder i proxy-grensesnittene som ville være forventet. - metoden
Proxy.isProxyClass
returnerer true ifit er bestått en proxy klasse-en klasse returnert avProxy.getProxyClass
eller klassen av et objekt returnedbyProxy.newProxyInstance
– og falsk ellers. Påliteligheten av denne metoden er viktig for evnen til å bruke denå ta sikkerhetsbeslutninger, så implementeringen bør ikke baretest hvis den aktuelle klassen strekker segjava.lang.reflect.Proxy
. -
java.security.ProtectionDomain
for en proxyclass er den samme som for systemklasser lastet av bootstrapclass loader, for eksempeljava.lang.Object
, fordi koden for en proxy-klasse genereres av klarert systemkode. Thisprotection domene vil vanligvis bli gittjava.security.AllPermission
.
Opprette En Proxy-Forekomst
Hver proxy-klasse har en offentlig konstruktør som tar oneargument, en implementering av grensesnittet InvocationHandler
.
Hver proxy-forekomst har et tilknyttet anropsbehandlingsobjekt,det som ble sendt til konstruktøren. I stedet for å bruke reflection API for å få tilgang til den offentlige konstruktøren, kan en proxyinstance også opprettes ved å ringeProxy.newProxyInstance
– metoden, som kombinerer handlinger av å ringe Proxy.getProxyClass
med invokingkonstruktøren med en invokasjonshåndterer.Proxy.newProxyInstance
kasterIllegalArgumentException
av samme grunner somProxy.getProxyClass
gjør.
Egenskaper For Proxy-Forekomst
en proxy-forekomst har følgende egenskaper:
- Gitt en proxy-forekomst
proxy
og et avgrensesnittene implementert av sin proxy-klasseFoo
, vil det etterfølgende uttrykket returnere sant:proxy instanceof Foo
og følgende cast-operasjon vil lykkes (i stedet for å kaste
ClassCastException
):(Foo) proxy
- den statiske
Proxy.getInvocationHandler
– metoden willreturn invocation handler knyttet til proxy instancepassed som argument. Hvis objektet som sendes tilProxy.getInvocationHandler
ikke er en proxy-forekomst, vil enIllegalArgumentException
bli kastet. - en grensesnittmetode-påkalling på en proxy-forekomst vil bli kodet og sendt til aktiveringsbehandlerens
invoke
– metode som beskrevet nedenfor.proxy-forekomsten selv vil bli sendt som det første argumentof
invoke
, som er av typenObject
.det andre argumentet som sendes til
invoke
, vil værejava.lang.reflect.Method
– forekomsten som svarer til interface-metoden som startes på proxy-forekomsten. DeklarasjonsklassenMethod
– objektet vil være grensesnittet som themethod ble deklarert i, som kan være et supergrensesnitt av proxyinterface som proxy-klassen arver metoden gjennom.det tredje argumentet som sendes til
invoke
, vil være anarray av objekter som inneholder verdiene til argumentene som er gått inn i metoden påkalling på proxy-forekomsten. Argumenter for primitivetypes er pakket inn i en forekomst av den aktuelle primitivewrapper-klassen, for eksempeljava.lang.Integer
ellerjava.lang.Boolean
. Implementeringen avinvoke
– metoden er fri til å endre innholdet i thisarray.verdien returnert av metoden
invoke
vil blireturverdien av metoden påkalling på proxy-forekomsten. Hvis den deklarerte returverdien til grensesnittmetoden er en primitivetype,må verdien som returneres avinvoke
være en instans av den tilsvarende primitive wrapper-klassen; ellers må den være en type som kan tilordnes den deklarerte returtypen. Hvis verdien returnert avinvoke
ernull
og interface-metodens returtype er primitiv, vil enNullPointerException
bli kastet av methodinvocation på proxy-forekomsten. Hvis verdien som returneres avinvoke
, ellers ikke er kompatibel med metodens deklarerte returtype som beskrevet ovenfor, vil enClassCastException
bli kastet av proxyinstance.hvis et unntak kastes av metoden
invoke
, vil det også bli kastet av metoden invocation på proxy-forekomsten.Unntakstypen må tilordnes til en av exception-typene som er deklarert i signaturen til grensesnittmetoden eller til de ukontrollerte unntakstypenejava.lang.RuntimeException
ellerjava.lang.Error
. Hvis et merket unntak kastes avinvoke
som ikke kan tilordnes til noen av exceptiontypes som er deklarert ithrows
– klausulen i interfacemethod, vil enUndeclaredThrowableException
bli kastet av metoden invocation på proxy-forekomsten.UndeclaredThrowableException
vil bli konstruert med unntak som ble kastet avinvoke
– metoden. - en påkalling av metodene
hashCode
,equals
ellertoString
som er deklarert ijava.lang.Object
i en proxy-forekomst, kodes og sendes til behandlerens metodeinvoke
på samme måte som grensesnittmetode-påkallinger kodes og sendes, som beskrevet ovenfor. Den deklarerende klassen avMethod
– objektet gikk tilinvoke
willbejava.lang.Object
. Andre offentlige metoder for en proxyinstance arvet frajava.lang.Object
blir ikke overstyrt av en proxy-klasse, så påkallelser av disse metodene oppfører seg som de gjør for forekomster avjava.lang.Object
.
Metoder Duplisert I MultipleProxy-Grensesnitt
når to eller flere grensesnitt i en proxy-klasse inneholder en metodemed samme navn og parametersignatur, blir rekkefølgen til proxyclassens grensesnitt betydelig. Når en slik duplicatemethod påberopes i en proxy-forekomst, vil Method
– objektet som sendes til aktiveringsbehandleren, ikke nødvendigvis være en hvis deklareringsklasse kan tilordnes fra referansetypen grensesnittet som proxyens metode ble påberopt gjennom. Thislimitation eksisterer fordi den tilsvarende metoden implementeringi den genererte proxy-klassen ikke kan bestemme hvilket grensesnitt den ble påkalt gjennom. Når en duplikatmetode er invokedon en proxy-forekomst, sendes derfor Method
– objektet for metodeni det fremste grensesnittet som inneholder metoden (enten direkte eller arvet gjennom et superinterface) i proxy-klassens liste over grensesnitt til behandlingsmetoden invoke
– metoden, uavhengig av referansetype gjennom hvilken metode-påkallingen skjedde.
hvis et proxy-grensesnitt inneholder en metode med samme navn andparameter-signatur som hashCode
,equals
eller toString
– metodene for java.lang.Object
, når en slik metode startes på aproxy-forekomsten, vil Method
– objektet som sendes til theinvocation handler, ha java.lang.Object
som itsdeclaring-klassen. Med andre ord, den offentlige, ikke-endelige metoder forjava.lang.Object
logisk foran alle proxyinterfaces for bestemmelse av hvilke Method
objekt å passere til påkallingsbehandleren.
Merk også at når en duplikatmetode sendes til aninvocation handler, kan metoden invoke
bare throwchecked unntakstyper som kan tilordnes til en av exceptiontypes i throws
– setningsdelen av metoden i allof proxy-grensesnittene som den kan påberopes gjennom. Hvis metodeninvoke
kaster et merket unntak som ikke kan tilordnes til noen av unntakstypene deklarert av metoden inone av proxy-grensesnittene som den kan påberopes gjennom, vil anunchecked UndeclaredThrowableException
bli kastetved påkallingen på proxy-forekomsten. Denne begrensningen betyr at ikke alle unntakstypene som returneres ved å påkallegetExceptionTypes
på Method
objectpassed til invoke
– metoden, nødvendigvis kan kastes med invoke
– metoden.
Serialisering
siden java.lang.reflect.Proxy
implementerer java.io.Serializable
, kan proxy-forekomster beserialisert, som beskrevet i denne delen. Hvis en proxy instancecontains en påkallingsbehandling som ikke kan tilordnes tiljava.io.Serializable
, vil imidlertid enjava.io.NotSerializableException
bli kastet hvis en slik forekomst er skrevet til enjava.io.ObjectOutputStream
. Merk at for proxyclasses vil implementering av java.io.Externalizable
ha samme effekt med hensyn til serialisering som implementering avjava.io.Serializable
: metodene writeExternal
og readExternal
for grensesnittet Externalizable
aldri bli påberopt på aproxy-forekomst (eller en invokasjonshåndterer) som en del av itsserialiseringsprosessen. Som med alle Class
– objekter, erClass
– objektet for en proxy-klasse alltid serialiserbar.
en proxy-klasse har ingen serialiserbare felt og enserialVersionUID
av 0L
. Med andre ord, når Class
– objektet for en proxy-klasse sendes til statisk lookup
– metoden for java.io.ObjectStreamClass
, vil den returnerteObjectStreamClass
– forekomsten ha følgende egenskaper:
- Å Påkalle sin
getSerialVersionUID
– metode vilreturn0L
. - Å Påkalle sin
getFields
– metode vil returnere en arrayof lengde null. - Påkalle sin
getField
metode med noenString
argument vil returnerenull
.
stream-protokollen for Objektserialisering støtter en typekode som heter TC_PROXYCLASSDESC
, som er et terminalsymbol i grammatikken for stream-formatet; dens type og verdi er definert av følgende konstantfelt i grensesnittetjava.io.ObjectStreamConstants
:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
grammatikken inneholder også følgende to regler, den førstebeing en alternativ utvidelse av den opprinnelige newClassDescrule:
newClassDesc:TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
proxyInterfaceName classAnnotationsuperClassDesc
proxyinterfacename:(utf)
når en ObjectOutputStream
serialiserer classdescriptor for en klasse som er en proxy-klasse, som bestemt omgå sin Class
objekt til metoden Proxy.isProxyClass
, bruker den TC_PROXYCLASSDESC
typekoden i stedet forTC_CLASSDESC
, etter reglene ovenfor. I expansion av proxyClassDescInfo er sekvensen av proxyinterfacename-elementer navnene på alle interfaces implementert av proxy-klassen, i den rekkefølgen de returneres ved å påkalle getInterfaces
– metoden på Class
– objektet. ClassAnnotation andsuperclassdesc-elementene har samme betydning som de gjør i theclassDescInfo-regelen. For en proxy-klasse, superClassDescis klassebeskrivelsen for sin superklasse,java.lang.reflect.Proxy
; inkludert denne beskrivelsentillater utviklingen av den serialiserte representasjonen av klassen Proxy
for proxy-forekomster.
for ikke-proxy-klasser kaller ObjectOutputStream
itsprotected annotateClass
– metoden for å tillate underklasser å skrive egendefinerte data til strømmen for en bestemt klasse. For proxyclasses, i stedet for annotateClass
, kalles followingmethod i java.io.ObjectOutputStream
withthe Class
– objektet for proxy-klassen:
protected void annotateProxyClass(Class cl) throws IOException;
standard implementering av annotateProxyClass
i ObjectOutputStream
gjør ingenting.
når enObjectInputStream
møter typekoden TC_PROXYCLASSDESC
, deserialiserer den classdescriptor for en proxy-klasse fra strømmen, formatert som beskrevet ovenfor. I stedet for å kalle sin resolveClass
– metode for å løse Class
– objektet for classdescriptor, kalles følgende metode ijava.io.ObjectInputStream
:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
listen over grensesnittnavn som ble deserialisert i proxyclass-beskrivelsen, sendes som interfaces
argumentto resolveProxyClass
.
standard implementering av resolveProxyClass
iObjectInputStream
returnerer resultatene av kallProxy.getProxyClass
med listen over Class
objekter for grensesnittene som er navngitt i parametereninterfaces
. Den Class
objectused for hvert grensesnittnavn i
er verdien retuned bycalling
Class.forName(i, false, loader)
derloader
er den første ikke-null klasse loader up theexecution stakken, ellernull
hvis ingen ikke-null klasse loadersare på stakken. Dette er det samme klasselastervalget som er gjort av default-oppførselen tilresolveClass
– metoden. Denne sameverdien avloader
er også klasselasteren sendt tilProxy.getProxyClass
. HvisProxy.getProxyClass
kaster enIllegalArgumentException
, vilresolveClass
kaste enClassNotFoundException
som inneholderIllegalArgumentException
.
siden en proxy-klasse aldri har sine egne serialiserbare felt, består classdata i stream-representasjonen av en proxy-instans helt av forekomstdataene for sin superklasse,java.lang.reflect.Proxy
. Proxy
har oneserializable-feltet, h
, som inneholder invocationhandler for proxy-forekomsten.
Eksempler
her er et enkelt eksempel som skriver ut en melding før og etter en metode påkalling på et objekt som implementerer en vilkårlig liste over grensesnitt:
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; }}
for å konstruere en DebugProxy
for en implementering av grensesnittet Foo
og ring en av metodene:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
Her er et eksempel på et verktøy behandlingsklasse som gir standard proxy virkemåte for metoder arvet frajava.lang.Object
og implementerer delegering av certainproxy metode besvergelser til forskjellige objekter avhengig av grensesnittet til den påberopte metoden:
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()); }}
Underklasser av Delegator
kan overstyreinvokeNotDelegated
for å implementere virkemåten til proxymethod-påkallinger som ikke skal delegeres direkte til andre objekter,og de kan overstyre proxyHashCode
,proxyEquals
og proxyToString
tooverride standard virkemåten til metodene proxy arverfra java.lang.Object
.
å konstruere en Delegator
for en implementering av Foo
grensesnitt:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
Merk at implementeringen av Delegator
– klassen ovenfor er ment å være mer illustrerende enn optimalisert; for eksempel, i stedet for å bufre og sammenligne Method
– objektene for hashCode
, equals
ogtoString
– metodene, kan det bare matche dem med deres navn, fordi ingen av disse metodenavnene er overbelastet i java.lang.Object
.