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
:
- alla
Class
– objekt iinterfaces
– arrayen måste representera gränssnitt, notclasses eller primitiva typer. - inga två element i
interfaces
arrayen får referera till identiskaClass
objekt. - alla gränssnittstyper måste vara synliga med namn genom specificerad klasslastare. Med andra ord, för klass loader
cl
och varje gränssnitti
, 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),åberopargetMethods
på dessClass
objectwill returnera en array avMethod
objekt som includeall av metoderna i dessa gränssnitt, och åberopargetMethod
kommer att hitta metoder i Proxy gränssnitt aswould vara förväntat. - metoden
Proxy.isProxyClass
returnerar true om det skickas en proxy-klass-en klass som returneras avProxy.getProxyClass
eller klassen för ett objekt som returneras avProxy.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 exempeljava.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 klassFoo
, 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 enIllegalArgumentException
att kastas. - en gränssnittsmetod anrop på en proxy instans kommer beencoded och skickas till anropshanteraren ’ s
invoke
metod som beskrivs nedan.proxyinstansen själv kommer att skickas som det första argumentet av
invoke
, som är av typenObject
.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 objektetMethod
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åsomjava.lang.Integer
ellerjava.lang.Boolean
. Implementeringen avinvoke
– 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 avinvoke
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 avinvoke
ärnull
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 ithrows
– klausulen i interfacemethod, kommer enUndeclaredThrowableException
att kastas med metoden invokation på proxyinstansen.UndeclaredThrowableException
kommer att byggas medundantaget som kastades medinvoke
– metoden. - en anrop av metoderna
hashCode
,equals
ellertoString
som deklareras ijava.lang.Object
på en proxyinstans kommer att kodas och skickas till anropshanterarensinvoke
– metod på samma sätt som gränssnittsmetoden anrop areencoded och skickas, som beskrivits ovan. Den deklarerande klassen avMethod
– objektet som skickas tillinvoke
kommer att varajava.lang.Object
. Andra offentliga metoder för en proxyinstance som ärvts frånjava.lang.Object
är inteöverridna av en proxyklass, så anrop av dessa metoder beter sig som de gör för instanser avjava.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 Method
objekt 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 åberopagetExceptionTypes
på Method
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
getSerialVersionUID
metod willreturn0L
. - åberopar dess
getFields
metod kommer att returnera en arrayof längd noll. - åberopar dess
getField
metod med någotString
argument kommer att returneranull
.
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_PROXYCLASSDESC
newHandle 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, ellernull
om 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.getProxyClass
kastar enIllegalArgumentException
, kommerresolveClass
att kasta enClassNotFoundException
som 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
.