Dynamiska Proxyklasser

innehåll

introduktion
dynamisk proxy API
serialisering
exempel

introduktion

en dynamisk proxyklass är en klass som implementerar en lista över gränssnitt som anges vid körning så att en metod som anropar genom ett av gränssnitten på en instans av klassen kommer att kodas och skickas till ett annat objekt via ett uniforminterface. Således kan en dynamisk proxyklass användas för att skapa entype-safe proxy-objekt för en lista över gränssnitt utan att krävaförgenerering av proxyklassen, till exempel med kompileringsverktyg.Metodanrop på en instans av en dynamisk proxyklass skickas till en enda metod i instansens invocationhandler, och de kodas med ett java.lang.reflect.Method – objekt som identifierar metoden som anropades och en array av typen Object som innehåller argumenten.

dynamiska proxyklasser är användbara för en applikation eller ett biblioteksom behöver tillhandahålla typsäker reflekterande sändning av anroppå objekt som presenterar Gränssnitts-API: er. Till exempel kan en applikation använda en dynamisk proxyklass för att skapa ett objekt som implementerarflera godtyckliga händelselyssnargränssnitt-gränssnitt som förlänger java.util.EventListener – för att bearbeta en variation av händelser av olika slag på ett enhetligt sätt, till exempel genom att logga alla sådana händelser till en fil.

Dynamic Proxy Class API

en dynamisk proxyklass (helt enkelt kallad proxyclass nedan) är en klass som implementerar en lista över gränssnittspecificeras vid körning när klassen skapas.

ett proxy-gränssnitt är ett sådant gränssnitt som ärimplementeras av en proxy-klass.

en proxyinstans är en instans av en proxyclass.

skapa en Proxyklass

proxyklasser, liksom instanser av dem, skapas med hjälp avde statiska metoderna för klassen java.lang.återspegla.Proxy.

metodenProxy.getProxyClass returnerar objektet java.lang.Class för en proxyklass som ges en classloader och en rad gränssnitt. Proxy klassen kommer att definierasi den angivna klassen loader och kommer att genomföra alla thesupplied gränssnitt. Om en proxyklass för samma permutation av gränssnitt redan har definierats i klasslastaren, kommer den befintliga proxyklassen att returneras.annars kommer en proxyklass för dessa gränssnitt att genereras dynamiskt och definieras i klasslastaren.

det finns flera begränsningar för de parametrar som kan passeras till Proxy.getProxyClass:

  • allaClass – objekt i interfaces – arrayen måste representera gränssnitt, notclasses eller primitiva typer.
  • inga två element i interfaces arrayen får referera till identiska Class objekt.
  • alla gränssnittstyper måste vara synliga med namn genom specificerad klasslastare. Med andra ord, för klass loadercl och varje gränssnitt i, måste followingexpression vara sant:
     Class.forName(i.getName(), false, cl) == i
  • alla icke-offentliga gränssnitt måste vara i samma paket;annars skulle det inte vara möjligt för proxyklassen att genomföra alla gränssnitt, oavsett vilket paket det är definierat i.
  • för varje uppsättning medlemsmetoder för de angivna gränssnitten somhar samma signatur:
    • om returtypen för någon av metoderna är en primitiv typ orvoid, måste alla metoder ha samma returtyp.
    • annars måste en av metoderna ha en returtyp som kan tilldelas alla returtyper för resten av metoderna.
  • den resulterande proxyklassen får inte överskrida några gränser som åläggsklasser av den virtuella maskinen. Till exempel kan VM begränsa antalet gränssnitt som en klass kan implementera till 65535; i det fallet får storleken på interfaces – arrayen inte överstiga 65535.

om någon av dessa begränsningar bryts,Proxy.getProxyClass kommer att kasta enIllegalArgumentException. Om argumentetinterfaces array eller något av dess element är null, kommer en NullPointerException att kastas.

Observera att ordningen för de angivna proxygränssnitten ärbetydande: två förfrågningar om en proxyklass med sammakombination av gränssnitt men i en annan ordning kommer att resultera i två distinkta proxyklasser. Proxy klasser kännetecknas av theorderen av deras proxy gränssnitt för att ge deterministicmethod åkallan kodning i de fall där två eller flera av proxyinterfaces delar en metod med samma namn och parametersignature; detta resonemang beskrivs mer i detalj isektionen nedan med titeln metoder dupliceras inMultiple Proxy gränssnitt.

så att en ny proxy klass inte behöver genereras eachtime Proxy.getProxyClass åberopas med sameclass loader och lista över gränssnitt, genomförandet av thedynamic proxy class API bör hålla en cache av genererade proxyklasser, knappat av deras motsvarande lastare och gränssnitt lista.Genomförandet bör vara noga med att inte hänvisa till classloaders, gränssnitt, och Proxy klasser på ett sådant sätt att preventclass lastare, och alla deras klasser, från att garbagecollected när så är lämpligt.

Proxyklassegenskaper

en proxyklass har följande egenskaper:

  • Proxyklasser är offentliga, slutliga och inte abstrakta.
  • det Okvalificerade namnet på en proxyklass är ospecificerat. Spaceof – klassnamnen som börjar med strängen "$Proxy" ska dock reserveras för proxyklasser.
  • en proxyklass utökas java.lang.reflect.Proxy.
  • en proxyklass implementerar exakt de angivna gränssnitten viddess skapande, i samma ordning.
  • om en proxyklass implementerar ett icke-offentligt gränssnitt, kommer det att definieras i samma paket som det gränssnittet. Annars är paketet i en proxyklass också ospecificerat. Observera att packagesealing inte hindrar en proxyklass från att framgångsrikt definieras i ett visst paket vid körning, och varken kommer klasser som redan definierats i samma klasslastare och samma paket med särskilda signerare.
  • eftersom en proxy klass implementerar alla gränssnitt specificedat dess skapande, åberopar getInterfaces på dessClass objekt kommer att returnera en array som innehåller samelist av gränssnitt (i den ordning som anges vid dess skapande),åberopar getMethods på dess Class objectwill returnera en array av Method objekt som includeall av metoderna i dessa gränssnitt, och åberopargetMethod kommer att hitta metoder i Proxy gränssnitt aswould vara förväntat.
  • metodenProxy.isProxyClass returnerar true om det skickas en proxy-klass-en klass som returneras av Proxy.getProxyClass eller klassen för ett objekt som returneras av Proxy.newProxyInstance – och false annars. Dentillförlitligheten av denna metod är viktig för förmågan att använda denatt fatta säkerhetsbeslut, så dess genomförande bör inte baratesta om klassen i fråga sträcker sigjava.lang.reflect.Proxy.
  • java.security.ProtectionDomain för en proxyklass är densamma som för systemklasser som laddas av bootstrapclass-lastaren, till exempel java.lang.Object, eftersom koden för en proxyklass genereras av betrodd systemkod. Thisprotection-domänen beviljas vanligtvisjava.security.AllPermission.

skapa en Proxyinstans

varje proxyklass har en offentlig konstruktör som tar enargument, en implementering av gränssnittet InvocationHandler.

varje proxyinstans har ett associerat anropshanteringsobjekt,det som skickades till dess konstruktör. I stället för att använda reflection API för att komma åt den offentliga konstruktören kan en proxyinstance också skapas genom att ringaProxy.newProxyInstance – metoden, som kombinerar åtgärderna för att ringa Proxy.getProxyClass med invokingthe constructor med en invocation hanterare.Proxy.newProxyInstance kastarIllegalArgumentException av samma skäl somProxy.getProxyClass gör.

Proxyinstansegenskaper

en proxyinstans har följande egenskaper:

  • givet en proxy instans proxy och en av theinterfaces implementeras av sin proxy klass Foo, thefollowing expression kommer att returnera true:
     proxy instanceof Foo

    och följande cast operation kommer att lyckas (snarare än kastainga ClassCastException):

     (Foo) proxy
  • den statiska Proxy.getInvocationHandler – metoden kommer attreturnera anropshanteraren associerad med proxy instancepassed som argument. Om objektet som skickas tillProxy.getInvocationHandler inte är en proxyinstans,kommer en IllegalArgumentException att kastas.
  • en gränssnittsmetod anrop på en proxy instans kommer beencoded och skickas till anropshanteraren ’ sinvoke metod som beskrivs nedan.

    proxyinstansen själv kommer att skickas som det första argumentet av invoke, som är av typen Object.

    det andra argumentet som skickas till invoke kommer att varajava.lang.reflect.Method – instansen som motsvarar interface-metoden som åberopas på proxyinstansen. Den deklarerande klassen av objektet Method kommer att vara det gränssnitt som metoden förklarades i, vilket kan vara ett supergränssnitt för proxygränssnittet som proxyklassen ärver metoden genom.

    det tredje argumentet som skickas till invoke kommer att vara enarray av objekt som innehåller värdena för argumenten som skickas inmetoden anrop på proxyinstansen. Argument av primitivetypes är inslagna i en instans av lämplig primitivewrapper-klass, såsom java.lang.Integer ellerjava.lang.Boolean. Implementeringen av invoke – metoden är fri att ändra innehållet i thisarray.

    värdet som returneras av metoden invoke kommer att bliåtervinningsvärdet för metoden anrop på proxyinstansen. Om det deklarerade returvärdet för gränssnittsmetoden är en primitivetype, måste värdet som returneras av invoke vara eninstans av motsvarande primitiva wrapper-klass; annars måste det vara en typ som kan tilldelas den deklarerade returtypen. Om värdet som returneras av invoke är null och gränssnittsmetoden returtyp är primitiv, kommer enNullPointerException att kastas av metodinvokationen på proxyinstansen. Om värdet som returneras avinvoke annars inte är kompatibelt med metoden ’ deklarerad returtyp som beskrivs ovan, kommer enClassCastException att kastas av proxyinstance.

    om ett undantag kastas med metoden invoke, kommer det också att kastas med metoden invokation på proxyinstansen.Undantagstypen måste kunna tilldelas antingen någon av undantagstyperna som deklareras i signaturen för gränssnittsmetoden eller till de okontrollerade undantagstypernajava.lang.RuntimeException ellerjava.lang.Error. Om ett markerat undantag kastas avinvoke som inte kan tilldelas någon av de exceptiontypes som deklareras i throws – klausulen i interfacemethod, kommer en UndeclaredThrowableExceptionatt kastas med metoden invokation på proxyinstansen. UndeclaredThrowableException kommer att byggas medundantaget som kastades med invoke – metoden.

  • en anrop av metodernahashCode, equals ellertoString som deklareras i java.lang.Object på en proxyinstans kommer att kodas och skickas till anropshanterarens invoke – metod på samma sätt som gränssnittsmetoden anrop areencoded och skickas, som beskrivits ovan. Den deklarerande klassen av Method – objektet som skickas till invoke kommer att vara java.lang.Object. Andra offentliga metoder för en proxyinstance som ärvts från java.lang.Objectär inteöverridna av en proxyklass, så anrop av dessa metoder beter sig som de gör för instanser av java.lang.Object.

metoder duplicerade i MultipleProxy-gränssnitt

när två eller flera gränssnitt i en proxyklass innehåller en metodmed samma namn och parametersignatur blir ordningen för proxyklassens gränssnitt betydande. När en sådan duplicatemethod åberopas på en proxyinstans kommer Method – objektet som skickas till anropshanteraren inte nödvändigtvis att vara den vars deklarerande klass kan tilldelas från referenstypen för gränssnittet som proxys metod åberopades genom. Denna begränsning existerar eftersom motsvarande metodimplementering i den genererade proxyklassen inte kan avgöra vilket gränssnitt den åberopades genom. Därför, när en dubblettmetod anropas i en proxyinstans, skickas Method – objektet för metoden i det främsta gränssnittet som innehåller metoden (antingen direkt eller ärvt genom ett superinterface) i proxyklassens lista över gränssnitt till anropshanterarensinvoke – metod, oavsett referenstypen genom vilken metodinkallandet inträffade.

om ett proxygränssnitt innehåller en metod med samma namn ochparametersignatur som hashCode,equals eller toString metoder förjava.lang.Object, när en sådan metod åberopas på aproxy-instans, kommer Method – objektet som skickas till invokationshanteraren att ha java.lang.Object som dessdeklarationsklass. Med andra ord, de offentliga, icke-slutliga metoderna förjava.lang.Object logiskt föregår alla proxyinterfaces för bestämning av vilka Methodobjekt som ska överföras till anropshanteraren.

Observera också att när en dubblettmetod skickas till aninvocation handler, kan metoden invoke endast kasta markerade undantagstyper som kan tilldelas en av undantagstyperna i throws – satsen i metoden i alla proxygränssnitt som den kan åberopas genom. Om metoden invoke kastar ett kontrollerat undantag som inte kan tilldelas någon av de undantagstyper som deklareras med metoden i ett av proxygränssnitten som det kan åberopas genom, kommer anunchecked UndeclaredThrowableException att kastas av anropet på proxyinstansen. Denna begränsning betyder att inte alla undantagstyper som returneras genom att åberopagetExceptionTypesMethod objektpasserat till invoke – metoden nödvändigtvis kan kastas framgångsrikt med invoke – metoden.

serialisering

eftersom java.lang.reflect.Proxy implementerarjava.io.Serializable kan proxyinstanser varaserialiseras, som beskrivs i detta avsnitt. Om en proxy instancecontains en anropshanterare som inte kan tilldelasjava.io.Serializable, kommer emellertid enjava.io.NotSerializableException att kastas om en sådan instans skrivs till enjava.io.ObjectOutputStream. Observera att för proxyclasses har implementering av java.io.Externalizable samma effekt med avseende på serialisering som implementering avjava.io.Serializable: metoderna writeExternal och readExternal i gränssnittetExternalizable kommer aldrig att åberopas på aproxy-instans (eller en anropshanterare) som en del av itsserialiseringsprocessen. Som med allaClass – objekt är Class – objektet för en proxyklass alwaysserializable.

en proxyklass har inga serialiserbara fält och enserialVersionUID av 0L. Med andra ord, när Class – objektet för en proxyklass skickas tillden statiskalookup – metoden för java.io.ObjectStreamClass, kommer den returnerade ObjectStreamClass – instansen att ha följandeegenskaper:

  • åberopar dess getSerialVersionUIDmetod willreturn 0L.
  • åberopar dess getFields metod kommer att returnera en arrayof längd noll.
  • åberopar dess getField metod med något String argument kommer att returnera null.

strömprotokollet för Objektserialisering stöder en typkod med namnet TC_PROXYCLASSDESC, som är en terminalsymbol i grammatiken för strömformatet; dess typ och värde definieras av följande konstanta fält i gränssnittetjava.io.ObjectStreamConstants :

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

grammatiken innehåller också följande två regler, den första är en alternativ utvidgning av den ursprungliga newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count>proxyInterfaceName classAnnotationsuperClassDesc

proxyinterfacename:
(utf)

när en ObjectOutputStream serialiserar classdescriptor för en klass som är en proxyklass, som bestäms förbi dess Class – objekt till metodenProxy.isProxyClass, använder denTC_PROXYCLASSDESC typkod istället förTC_CLASSDESC, enligt reglerna ovan. I theexpansion av proxyClassDescInfo, sekvensen av proxyinterfacename objekt är namnen på alla theinterfaces implementeras av proxy klassen, i den ordning som theyare returneras genom att åberopa getInterfaces metoden på Class objekt. Classannotation andsuperClassDesc-objekten har samma betydelse som de gör iclassdescinfo-regeln. För en proxyklass är superClassDescis klassbeskrivaren för sin superklass,java.lang.reflect.Proxy; inklusive denna descriptortillåter utvecklingen av den serialiserade representationen av klassen Proxy för proxyinstanser.

för icke-proxy klasser, ObjectOutputStream kallar itsprotected annotateClass metod för att tillåta underklasser towrite anpassade data till strömmen för en viss klass. För proxyklasser, istället för annotateClass, kallas följandemetoden i java.io.ObjectOutputStream Med Class – objektet för proxyklassen:

 protected void annotateProxyClass(Class cl) throws IOException;

standardimplementeringen av annotateProxyClass iObjectOutputStream gör ingenting.

när en ObjectInputStream stöter på typkodenTC_PROXYCLASSDESC, deserialiserar den classdescriptor för en proxyklass från strömmen, formaterad sombeskrivet ovan. Istället för att anropa dess resolveClass – metod för att lösa Class – objektet för classdescriptor, kallas följande metod ijava.io.ObjectInputStream :

 protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;

listan över gränssnittsnamn som deserialiserades i proxyclass descriptor skickas som interfaces argumentto resolveProxyClass.

standardimplementeringen avresolveProxyClass iObjectInputStream returnerar resultaten av anropetProxy.getProxyClass med listan överClass objekt för gränssnitten som heter i parametern interfaces. Den Class objectused för varje gränssnitt namni är värdet retuned bycalling

 Class.forName(i, false, loader)

därloaderär den första icke-null klass loader up theexecution stack, ellernullom inga icke-null klass loadersare på stacken. Detta är samma klasslastare val som gjorts avstandard beteende förresolveClass– metoden. Denna samevalue avloaderär också klassen lastaren skickas tillProxy.getProxyClass. OmProxy.getProxyClasskastar enIllegalArgumentException, kommerresolveClassatt kasta enClassNotFoundExceptionsom innehållerIllegalArgumentException.

eftersom en proxyklass aldrig har sina egna serialiserbara fält, består classdata i streamrepresentationen av en proxy instanceconsists helt av instansdata för sin superklass,java.lang.reflect.Proxy. Proxy har oneserializable fält, h, som innehåller invocationhandler för proxy instans.

exempel

här är ett enkelt exempel som skriver ut ett meddelande före ochefter en metod åkallan på ett objekt som implementerar en godtyckliglista av gränssnitt:

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; }}

att konstruera en DebugProxy för en implementering av Foo gränssnitt och ringa en av dess metoder:

 Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);

här är ett exempel på en utility invocation hanterare klass som ger standard proxy beteende för metoder ärvs frånjava.lang.Object och implementerar delegering av certainproxy metod anrop till olika objekt beroende på gränssnittet för den anropade 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 åsidosättainvokeNotDelegated för att implementera beteendet hos proxymethod-anrop som inte ska delegeras direkt till andra objekt,och de kan åsidosätta proxyHashCode,proxyEquals och proxyToString tooverride standardbeteendet för de metoder som proxy ärverfrån java.lang.Object.

att konstruera en Delegator för en implementering av Foo gränssnitt:

 Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));

Observera att implementeringen av Delegator – klassenges ovan är avsedd att vara mer illustrativ än optimerad; till exempel, istället för att cacha och jämföra Method – objekten för hashCode, equals ochtoString – metoderna, kan det bara matcha dem med deras strängnamn, eftersom ingen av dessa metodnamn är överbelastade ijava.lang.Object.

Lämna ett svar

Din e-postadress kommer inte publiceras.