Dynamische Proxy-klassen

inhoud

introductie
dynamische Proxy-API
serialisatie
voorbeelden

introductie

een dynamische proxy-klasse is een klasse die een lijst van interfaces implementeert die tijdens runtime zijn gespecificeerd, zodat een methode-aanroep via een van de interfaces op een instantie van de klasse gecodeerd en verzonden wordt naar een ander object via een uniforminterface. Zo kan een dynamische proxy klasse worden gebruikt om een type-veilig proxy object te maken voor een lijst van interfaces zonder dat vooraf generatie van de proxy klasse vereist is, zoals met compile-time tools.Methode aanroepingen op een instantie van een dynamische proxy klasse worden verzonden naar een enkele methode in de aanroephandler van de instantie, en ze zijn gecodeerd met eenjava.lang.reflect.Method object dat de methode identificeert die werd aangeroepen en een array van type Objectdie de argumenten bevat.

dynamische proxyklassen zijn nuttig voor een toepassing of bibliotheekdie typeveilige reflecterende verzending van aanroepingen op objecten met interface-API ‘ s moet bieden. Een toepassing kan bijvoorbeeld een dynamische proxy-Klasse gebruiken om een object te maken dat meerdere willekeurige gebeurtenislistener-interfaces implementeert– interfaces die java.util.EventListener uitbreiden– om een verscheidenheid aan gebeurtenissen van verschillende typen op een uniforme manier te verwerken, zoals door al dergelijke gebeurtenissen naar een bestand te loggen.

dynamische Proxy-klasse API

een dynamische proxy-klasse (hieronder simpelweg een proxy-klasse genoemd) is een klasse die een lijst implementeert van interfaces die gespecificeerd zijn tijdens runtime wanneer de klasse wordt aangemaakt.

een proxy-interface is zo ‘ n interface die wordt uitgevoerd door een proxy-klasse.

een proxy-instantie is een instantie van een proxyklasse.

het maken van een Proxy Klasse

Proxy klassen, evenals instanties van hen, worden gemaakt met behulp van de statische methoden van de klasse java.lang.weerspiegelen.Proxy.

de Proxy.getProxyClass methode geeft hetjava.lang.Class object terug voor een proxy klasse gegeven een classloader en een array van interfaces. De proxy klasse zal worden gedefinieerd in de opgegeven klasse loader en zal alle van de geleverde interfaces implementeren. Als een proxy-klasse voor dezelfde permutatie van interfaces al is gedefinieerd in de class loader, dan zal de bestaande proxy-klasse worden geretourneerd; anders zal een proxy-klasse voor deze interfaces dynamisch worden gegenereerd en gedefinieerd in de class loader.

er zijn verschillende beperkingen voor de parameters die kunnen worden toegepast op Proxy.getProxyClass:

  • alle Class objecten in de interfaces array moeten interfaces, notclasses of primitieve types vertegenwoordigen.
  • geen twee elementen in de interfaces array mogen verwijzen naar identieke Class objecten.
  • alle interfacetypen moeten zichtbaar zijn op naam via de gespecificeerde klassenlader. Met andere woorden, voor klasse Ladercl en elke interface i moet de volgende expressie waar zijn:
     Class.forName(i.getName(), false, cl) == i
  • alle niet-publieke interfaces moeten in hetzelfde pakket zitten;anders zou het niet mogelijk zijn voor de proxy klasse om alle interfaces uit te voeren, ongeacht in welk pakket het is gedefinieerd.
  • voor elke reeks ledenmethoden van de gespecificeerde interfaces die dezelfde handtekening hebben:
    • als het retourtype van een van de methoden een primitief type orvoid is, moeten alle methoden hetzelfde retourtype hebben.
    • anders moet een van de methoden een retourtype hebben dat kan worden toegeschreven aan alle retourtypes van de rest van de methoden.
  • de resulterende proxy-klasse mag geen limieten overschrijden die door de virtuele machine aan klassen zijn opgelegd. De VM kan bijvoorbeeld het aantal interfaces dat een klasse kan implementeren beperken tot 65535; in dat geval mag de grootte van de interfaces array niet groter zijn dan 65535.

als een van deze beperkingen wordt geschonden, zalProxy.getProxyClass eenIllegalArgumentExceptiongooien. Als het interfaces array argument of een van de elementennull is, zal een NullPointerException worden omvergeworpen.

merk op dat de volgorde van de opgegeven proxy-interfaces significant is: twee verzoeken voor een proxy-klasse met dezelfde combinatie van interfaces maar in een andere volgorde zullen resulteren in twee verschillende proxy-klassen. Proxy klassen worden onderscheiden door de volgorde van hun proxy interfaces om deterministicmethod aanroep codering in gevallen waarin twee of meer van de proxy interfaces delen een methode met dezelfde naam en parametersignatuur; deze redenering wordt in meer detail beschreven in de sectie hieronder getiteld methoden gedupliceerd inMultiple Proxy Interfaces.

zodat een nieuwe proxy-klasse niet elke keer hoeft te worden gegenereerd Proxy.getProxyClass wordt aangeroepen met dezelfde klasse-lader en lijst van interfaces, moet de implementatie van de dynamische proxy-klasse API een cache van gegenereerde proxy-klassen bijhouden, gekoppeld aan de bijbehorende laders en interface-lijst.De implementatie moet voorzichtig zijn om niet te verwijzen naar de classloaders, interfaces, en proxy klassen op een zodanige manier dat class laders, en al hun klassen, te voorkomen dat garbage collected wanneer van toepassing.

Proxy Klasse eigenschappen

een proxy klasse heeft de volgende eigenschappen:

  • Proxy klassen zijn openbaar, definitief, en niet abstract.
  • de niet-gekwalificeerde naam van een proxy-klasse is niet gespecificeerd. De ruimte van klassenamen die begint met de tekenreeks "$Proxy" moet echter gereserveerd worden voor proxy-klassen.
  • een proxy-klasse breidt java.lang.reflect.Proxyuit.
  • een proxy-klasse implementeert precies de interfaces die zijn opgegeven voor het aanmaken van atits, in dezelfde volgorde.
  • als een proxy klasse een niet-publieke interface implementeert, dan zal deze in hetzelfde pakket als die interface worden gedefinieerd. Anders is het pakket van een proxy klasse ook niet gespecificeerd. Merk op dat packagesealing niet zal voorkomen dat een proxy class succesvol wordt gedefinieerd in een bepaald pakket tijdens runtime, en evenmin zalclasses al gedefinieerd in dezelfde klasse loader en hetzelfde pakket met bepaalde ondertekenaars.
  • aangezien een proxy Klasse alle interfaces implementeert die bij het aanmaken zijn opgegeven, zal getInterfaces op zijnClass object een array retourneren die dezelfde lijst van interfaces bevat (in de volgorde die is opgegeven bij het aanmaken), getMethods op zijn Class object zal een array van Method objecten retourneren die alle methoden in die interfaces bevatten, engetMethod zal methoden in de proxy interfaces vinden zoals verwacht zou worden.
  • de methode Proxy.isProxyClass geeft true terug als het wordt doorgegeven aan een proxy-Klasse– Een klasse geretourneerd doorProxy.getProxyClass of de klasse van een object geretourneerd door Proxy.newProxyInstance— en anders false. De betrouwbaarheid van deze methode is belangrijk voor de mogelijkheid om het te gebruiken om beveiligingsbeslissingen te nemen, dus de implementatie ervan moet niet alleen testen of de klasse in kwestie zich uitbreidt totjava.lang.reflect.Proxy.
  • de java.security.ProtectionDomain van een proxy-klasse is dezelfde als die van systeemklassen die door de bootstrapclass-lader worden geladen, zoals java.lang.Object, omdat de code voor een proxy-klasse wordt gegenereerd door vertrouwde systeemcode. Dit beschermdomein wordt doorgaans verleendjava.security.AllPermission.

een proxy-Instance aanmaken

elke proxy-klasse heeft één publieke constructor die één jaar geleden is gestart, een implementatie van de interface InvocationHandler.

elke proxy-instantie heeft een geassocieerd aanroep-handler-object,het object dat aan de constructor is doorgegeven. In plaats van gebruik te maken van de reflectie-API om toegang te krijgen tot de publieke constructor, kan een proxy-instance ook worden gemaakt door deProxy.newProxyInstance methode aan te roepen, die de handelingen van Proxy.getProxyClass combineert met het aanroepen van de constructor met een aanroephandler.Proxy.newProxyInstance gooitIllegalArgumentException om dezelfde redenen alsProxy.getProxyClass.

Proxy-Instance Eigenschappen

Een proxy instantie heeft de volgende eigenschappen:

  • Gegeven een proxy-instance proxy en een van theinterfaces uitgevoerd door de proxy klasse Foo, de volgende expressie return true:
     proxy instanceof Foo

    en de volgende cast operatie zal slagen (in plaats van throwinga ClassCastException):

     (Foo) proxy
  • De statische Proxy.getInvocationHandler methode willreturn het inroepen van de handler gekoppeld aan de proxy instancepassed als argument. Als het object dat aanProxy.getInvocationHandler is doorgegeven Geen proxy-instantie is, dan zal een IllegalArgumentException worden gegooid.
  • een interfacemethode aanroep op een proxy-instantie wordt gecodeerd en verzonden naar deinvoke methode van de aanroepafhandeling, zoals hieronder beschreven.

    de proxy-instantie zelf zal worden doorgegeven als het eerste argument van invoke, dat van het type Objectis.

    het tweede argument dat wordt doorgegeven aan invoke zal de java.lang.reflect.Method instantie zijn die overeenkomt met de interface methode die wordt aangeroepen op de proxy instantie. De declarerende klasse van het Method object zal de interface zijn waarin de methode is gedeclareerd, wat een superinterface kan zijn van het proxyinterface waar de proxy-klasse de methode mee erft.

    het derde argument dat wordt doorgegeven aan invoke zal een array zijn van objecten die de waarden bevatten van de argumenten die worden doorgegeven in de methode aanroep op de proxy instantie. Argumenten van primitivetypes worden verpakt in een instantie van de juiste primitivewrapper klasse, zoals java.lang.Integer ofjava.lang.Boolean. De implementatie van deinvoke methode is vrij om de inhoud van deze serie te wijzigen.

    de waarde geretourneerd door de invoke methode wordt de retourwaarde van de methode aanroep op de proxy instantie. Als de gedeclareerde return-waarde van de interface-methode een primitivetype is, dan moet de waarde die invoke wordt geretourneerd een instantie zijn van de corresponderende primitieve wrapper-klasse; anders moet het een type zijn dat kan worden toegewezen aan het gedeclareerde return-type. Als de waarde geretourneerd door invoke null is en het returntype van de interface methode primitief is, dan zal eenNullPointerException worden gegooid door de methodinvocation op de proxy instantie. Als de metinvoke geretourneerde waarde anders niet compatibel is met het gedeclareerde retourtype van de methode zoals hierboven beschreven, zal eenClassCastException worden gegooid door de proxyinstance.

    als een uitzondering wordt gegooid door de invoke methode, zal deze ook worden gegooid door de methode aanroep op de proxy instantie.Het type van de uitzondering moet kunnen worden toegewezen aan een van de in de handtekening van de interface-methode gedeclareerde exceptiontypes of aan de niet-aangevinkte exceptiontypesjava.lang.RuntimeException ofjava.lang.Error. Als een aangevinkte uitzondering wordt gegooid metinvoke die niet kan worden toegewezen aan een van de uitzonderingstypes die zijn aangegeven in de throws clausule van de interfacemethod, dan zal een UndeclaredThrowableExceptionworden gegooid door de methode aanroep op de proxy instantie. DeUndeclaredThrowableException wordt geconstrueerd met uitzondering van de invoke methode.

  • een aanroep van de hashCode,equals of toString injava.lang.Object gedeclareerde methoden op een proxy-instantie zal worden gecodeerd en verzonden naar de invokemethode van de aanroepbewerker op dezelfde manier als de aanroepmethoden gecodeerd en verzonden worden, zoals hierboven beschreven. De declarerende klasse van het Method object dat is doorgegeven aan invoke is java.lang.Object. Andere openbare methoden van een proxy die van java.lang.Object zijn geërfd, worden niet overschreven door een proxy-klasse, zodat aanroepingen van deze methoden zich gedragen zoals ze doen bij gevallen van java.lang.Object.

methoden gedupliceerd in MultipleProxy-Interfaces

wanneer twee of meer interfaces van een proxy-klasse een methode met dezelfde naam en parameterhandtekening bevatten, wordt de volgorde van de interfaces van de proxy-klasse significant. Wanneer een dergelijke duplicatemethod wordt aangeroepen op een proxy-instantie, zal het Method – object dat wordt doorgegeven aan de aanroepafhandeling niet noodzakelijk het object zijn waarvan de declarerende klasse kan worden toegewezen aan het referentietype van de interface waarmee de proxy-methode werd aangeroepen. Deze beperking bestaat omdat de corresponderende methodeimplementatiein de gegenereerde proxy klasse niet kan bepalen via welke interface het werd aangeroepen. Daarom, wanneer een dubbele methode wordt aangeroepen op een proxy instantie, wordt het Method object voor de methode in de belangrijkste interface die de methode bevat (direct of geërfd via een superinterface) in de proxy klasse lijst van interfaces doorgegeven aan de aanroep handlerinvoke methode, ongeacht het referentietype waarmee de methode aanroep plaatsvond.

als een proxy-interface een methode bevat met dezelfde naam enparameterhandtekening als dehashCode, equals, oftoString methoden van java.lang.Object, wanneer een dergelijke methode wordt aangeroepen op een proxy-instantie, zal het Method – object dat wordt doorgegeven aan de uitnodiging-handler java.lang.Object als declaring-klasse hebben. Met andere woorden, de openbare, niet-definitieve methoden vanjava.lang.Object gaan logischerwijs vooraf aan alle proxyinterfaces voor de bepaling waarvan Methodbezwaar maakt om door te geven aan de aanroepafhandeling.

merk ook op dat wanneer een dubbele methode wordt verzonden naar eeninvocatie-handler, de invoke methode alleen gechecked uitzonderingstypes mag bevatten die kunnen worden toegewezen aan een van de uitzonderingstypes in de throws clausule van de methode in alle proxy-interfaces waarmee het kan worden aangeroepen. Als deinvoke methode een aangevinkte uitzondering gooit die niet kan worden toegewezen aan een van de typen uitzonderingen die zijn gedeclareerd door de methode in een van de proxy interfaces waarmee het kan worden aangeroepen, dan zal een niet-aangevinkte UndeclaredThrowableException worden geworpen door de aanroep op de proxy instantie. Deze beperking houdt in dat niet alle soorten uitzonderingen die worden geretourneerd doorgetExceptionTypes aan te roepen op het Method object dat aan de invoke methode wordt toegekend, noodzakelijkerwijs met succes kunnen worden uitgevoerd door de invoke methode.

serialisatie

aangezien java.lang.reflect.Proxyjava.io.Serializable implementeert, kunnen proxy-instanties worden gehererialiseerd, zoals beschreven in deze sectie. Als een proxy instance echter een aanroephandler bevat die niet aanjava.io.Serializable kan worden toegewezen, dan zal eenjava.io.NotSerializableException worden gegooid als een dergelijke instantie naar eenjava.io.ObjectOutputStreamwordt geschreven. Merk op dat Voor proxyklassen het implementeren van java.io.Externalizable hetzelfde effect heeft met betrekking tot serialisatie als het implementeren vanjava.io.Serializable: de writeExternalen readExternal methoden van deExternalizable interface zullen nooit worden aangeroepen op een proxy instantie (of een aanroep handler) als onderdeel van het serialisatieproces. Zoals met alle Class objecten is het Class object voor een proxy-Klasse altijd serialiseerbaar.

een proxy-klasse heeft geen serialiseerbare velden en eenserialVersionUID van 0L. Met andere woorden, wanneer het Class object voor een proxy klasse wordt doorgegeven aan de statische lookup methode vanjava.io.ObjectStreamClass, zal de geretourneerdeObjectStreamClass instantie de volgende eigenschappen hebben:

  • het aanroepen van de getSerialVersionUID methode zal 0Ldraaien.
  • door gebruik te maken van de getFields methode zal een array van lengte nul worden geretourneerd.
  • het aanroepen van de getField methode met eenString argument geeft nullterug.

het stream-protocol voor Objectserialisatie ondersteunt een typecode met de naam TC_PROXYCLASSDESC, wat een terminalsymbool is in de grammatica voor het stream-formaat; het type en de waarde aredefined door de volgende constante veld in dejava.io.ObjectStreamConstants interface:

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

De grammatica bevat ook de volgende twee regels, de firstbeing een alternatieve uitbreiding van de oorspronkelijke newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count>proxyInterfaceName classAnnotationsuperClassDesc

proxyInterfaceName:
(utf)

wanneer een ObjectOutputStream de classdescriptor serialiseert voor een klasse die een proxy-klasse is, zoals bepaald door het Class object te omzeilen van deProxy.isProxyClass methode, gebruikt het deTC_PROXYCLASSDESC type code in plaats vanTC_CLASSDESC, volgens de bovenstaande regels. In de expansion of proxyClassDescInfo is de volgorde van proxy-interfacename-items de namen van alle interfaces die door de proxy-klasse zijn geïmplementeerd, in de volgorde dat ze worden geretourneerd door de getInterfaces – methode op het Class – object aan te roepen. De classannotatie-en superclassdesc-items hebben dezelfde betekenis als in de classdescinfo-regel. Voor een proxy-klasse is superclassdescriptor de class-descriptor voor zijn superklasse,java.lang.reflect.Proxy; inclusief deze descriptor maakt de evolutie van de seriële representatie van de klasse Proxy voor proxy-instanties mogelijk.

voor non-proxy klassen, ObjectOutputStream roept de beveiligde annotateClass methode aan om subklassen toe te staan aangepaste gegevens op te slaan in de stream voor een bepaalde klasse. Voor proxyklassen wordt in plaats van annotateClass de volgende methode in java.io.ObjectOutputStream aangeroepen met het Class object voor de proxy-klasse:

 protected void annotateProxyClass(Class cl) throws IOException;

de standaard implementatie van annotateProxyClass inObjectOutputStream doet niets.

wanneer een ObjectInputStream de typecodeTC_PROXYCLASSDESC tegenkomt, deserialiseert het de classdescriptor voor een proxy-klasse uit de stream, opgemaakt zoals hierboven beschreven. In plaats van de resolveClassmethode aan te roepen om het Class object voor de classdescriptor op te lossen, wordt de volgende methode injava.io.ObjectInputStream aangeroepen:

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

de lijst van interfacenamen die gedeserialiseerd werden in de proxyclass descriptor wordt doorgegeven als interfaces argumentto resolveProxyClass.

de standaard implementatie van resolveProxyClass inObjectInputStream geeft de resultaten van het aanroepen vanProxy.getProxyClass met de lijst van Class objecten voor de interfaces genoemd in deinterfaces parameter. Het Class object dat voor elke interfacenaam i wordt gebruikt, is de waarde die wordt retuned bij het ophalen

 Class.forName(i, false, loader)

waarbijloaderde eerste Niet-null-klasse-lader op de uitvoerstack is, ofnullals er geen Niet-null-klasse-laders op de stack staan. Dit is dezelfde klasse Lader keuze gemaakt door de default gedrag van deresolveClassmethode. Deze samevalue vanloaderis ook de klasse Lader doorgegeven aanProxy.getProxyClass. AlsProxy.getProxyClasseenIllegalArgumentExceptiongooit, zalresolveClasseenClassNotFoundExceptiongooien die deIllegalArgumentExceptionbevat.

omdat een proxy-klasse nooit zijn eigen serialiseerbare velden heeft, bestaat de class-gegevens in de stream-representatie van een proxy-instance volledig uit de instance-gegevens voor zijn superklasse,java.lang.reflect.Proxy. Proxy heeft een eenerialiseerbaar veld, h, dat de aanroephandler voor de proxy-instantie bevat.

voorbeelden

hier is een eenvoudig voorbeeld dat een bericht voor en na een methodeaanroep afdrukt op een object dat een arbitraire lijst van interfaces implementeert:

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

een DebugProxy construeren voor een implementatie van de Foo interface en een van zijn methoden aanroepen:

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

hier is een voorbeeld van een utility aanroep handler klasse die standaard proxy gedrag voor methoden overgenomen vanjava.lang.Object en implementeert delegatie van bepaalde proxy methode aanroepingen aan verschillende objecten afhankelijk van deinterface van de aangeroepen methode:

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

subklassen van Delegator kunneninvokeNotDelegated overschrijven om het gedrag van proxymethod-aanroepingen te implementeren die niet direct aan andere objecten mogen worden gedelegeerd,en ze kunnen proxyHashCode,proxyEquals en proxyToString overschrijven om het standaardgedrag van de methoden die de proxy erft van java.lang.Objectte omzeilen.

een Delegator construeren voor een implementatie van de interface Foo :

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

merk op dat de toepassing van de bovengenoemde klasse Delegator meer illustratief is dan geoptimaliseerd; bijvoorbeeld, in plaats van de Methodobjecten voor de hashCode, equals entoString te cachen en te vergelijken, zou het ze gewoon kunnen matchen met hun string namen, omdat geen van deze methodenamen overbelast zijn injava.lang.Object.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.