Dynamic Proxy Classes

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 i interfaces – matrisen må representere grensesnitt, ikke klasser eller primitive typer.
  • ingen to elementer i interfaces – matrisen kan referere til identiske Class objekter.
  • Alle grensesnittypene må være synlige med navn gjennom thespecified class loader. Med andre ord, for klasselastercl og hvert grensesnitt i, 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.getProxyClasskaste 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åkalle getMethods på sitt Class – objektvil returnere en matrise med Method 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 returnedby Proxy.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 eksempel java.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-klasse Foo, 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 en IllegalArgumentException bli kastet.
  • en grensesnittmetode-påkalling på en proxy-forekomst vil bli kodet og sendt til aktiveringsbehandlerensinvoke – metode som beskrevet nedenfor.

    proxy-forekomsten selv vil bli sendt som det første argumentof invoke, som er av typen Object.

    det andre argumentet som sendes til invoke, vil være java.lang.reflect.Method – forekomsten som svarer til interface-metoden som startes på proxy-forekomsten. Deklarasjonsklassen Method – 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 eksempel java.lang.Integer eller java.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 av invoke 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 av invoke er null 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 i throws – klausulen i interfacemethod, vil en UndeclaredThrowableExceptionbli kastet av metoden invocation på proxy-forekomsten. UndeclaredThrowableException vil bli konstruert med unntak som ble kastet av invoke – metoden.

  • en påkalling av metodenehashCode, equals ellertoString som er deklarert i java.lang.Object i en proxy-forekomst, kodes og sendes til behandlerens metode invokepå samme måte som grensesnittmetode-påkallinger kodes og sendes, som beskrevet ovenfor. Den deklarerende klassen av Method – objektet gikk til invoke willbe java.lang.Object. Andre offentlige metoder for en proxyinstance arvet fra java.lang.Object blir ikke overstyrt av en proxy-klasse, så påkallelser av disse metodene oppfører seg som de gjør for forekomster av java.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 Methodobjekt å 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åkallegetExceptionTypesMethod 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 writeExternalog 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 vilreturn 0L.
  • Å Påkalle sin getFields – metode vil returnere en arrayof lengde null.
  • Påkalle sin getField metode med noen String argument vil returnere null.

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_PROXYCLASSDESCnewHandle 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)

derloaderer den første ikke-null klasse loader up theexecution stakken, ellernullhvis ingen ikke-null klasse loadersare på stakken. Dette er det samme klasselastervalget som er gjort av default-oppførselen tilresolveClass– metoden. Denne sameverdien avloaderer også klasselasteren sendt tilProxy.getProxyClass. HvisProxy.getProxyClasskaster enIllegalArgumentException, vilresolveClasskaste enClassNotFoundExceptionsom 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.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.