Dynamic Proxy Classes

Contents

Úvod
Dynamic Proxy API
serializace
příklady

Úvod

dynamic proxy class je třída, která implementuje seznam rozhraní zadaných za běhu tak, že metoda vyvolání přes jedno z rozhraní na instanci třídy bude kódována a odeslána do jiného objektu přes uniforminterface. Tak, dynamic proxy třída může být použita k vytvoření atype-safe proxy objekt pro seznam rozhraní, aniž by requiringpre-generace třídy proxy, například s nástroji kompilace-time.Vyvolání metody na instanci dynamické třídy proxy jsou přiřazeny k jedné metodě v invocationhandler instance a jsou kódovány objektemjava.lang.reflect.Method identifikujícím vyvolanou metodu a polem typu Objectobsahujícím argumenty.

dynamické třídy proxy jsou užitečné pro aplikaci nebo knihovnukteré musí poskytovat typově bezpečné reflexní odeslání objektů invocationson, které představují rozhraní API. Například aplikace může použít dynamickou třídu proxy k vytvoření objektu, který implementuje více libovolných rozhraní posluchačů událostí– rozhraní, která rozšiřují java.util.EventListener — k jednotnému zpracování různých událostí různých typů, jako je například blokování všech takových událostí do souboru.

Dynamic Proxy Class API

třída dynamic proxy (jednoduše označovaná jako proxyclass níže) je třída, která implementuje seznam rozhraní specifikovaných za běhu, když je třída vytvořena.

rozhraní proxy je takové rozhraní, které jeprovedené třídou proxy.

instance proxy je instance proxyclass.

Vytvoření třídy Proxy

třídy Proxy, stejně jako jejich instance, jsou vytvořeny pomocí statických metod třídy java.lang.odrazit.Zmocněnec.

metoda Proxy.getProxyClass vrací objektjava.lang.Class pro třídu proxy danou classloaderem a řadou rozhraní. Třída proxy bude definována v zadaném zavaděči třídy a bude implementovat všechna dodávaná rozhraní. Pokud již byla v zavaděči třídy definována třída proxy pro stejnou permutaci rozhraní, bude vrácena stávající třída proxy; jinak bude třída proxy pro tato rozhraní generována dynamicky a definována v zavaděči třídy.

existuje několik omezení parametrů, které mohou být předány Proxy.getProxyClass:

  • všechny objekty Class v poliinterfaces musí představovat rozhraní, nottřídy nebo primitivní typy.
  • žádné dva prvky v poli interfaces nesmějí odkazovat na identické Class objekty.
  • všechny typy rozhraní musí být viditelné podle názvu přes zadaný zavaděč třídy. Jinými slovy, pro class loadercl a každé rozhraní i musí být následující výraz pravdivý:
     Class.forName(i.getName(), false, cl) == i
  • Všechna neveřejná rozhraní musí být ve stejném balíčku; jinak by nebylo možné, aby třída proxy implementovala všechna rozhraní bez ohledu na to, v jakém balíčku je definována.
  • pro libovolnou sadu členských metod specifikovaných rozhraní, která mají stejný podpis:
    • pokud je návratový typ některé z metod primitivním typem orvoid, musí mít všechny metody stejný návratový typ.
    • v opačném případě musí mít jedna z metod typ návratu, který lze přiřadit ke všem typům návratu ostatních metod.
  • výsledná třída proxy nesmí překročit žádné mezitřídy virtuálním strojem. Například VM může omezit počet rozhraní, které může třída implementovat, na 65535; v tomto případě nesmí velikost pole interfaces překročit 65535.

pokud je některá z těchto omezení porušena,Proxy.getProxyClass hodíIllegalArgumentException. Pokud jeinterfaces argument pole nebo některý z jeho prvkůnull, bude NullPointerException.

Všimněte si, že pořadí specifikovaných proxy rozhraní je významné: dvě žádosti o třídu proxy se stejnou kombinací rozhraní, ale v jiném pořadí, budou mít za následek dvě odlišné třídy proxy. Třídy Proxy se vyznačují uspořádáním jejich rozhraní proxy, aby poskytly kódování vyvolání deterministicmethod v případech, kdy dvě nebo více rozhraní proxyinterfaces sdílejí metodu se stejným názvem a parametersignature; toto uvažování je podrobněji popsáno v níže uvedené části s názvem Metody duplikované v více proxy rozhraních.

aby nová třída proxy nemusela být generována pokaždé Proxy.getProxyClass je vyvolána se sameclass loader a seznamem rozhraní, implementace thedynamic proxy class API by měla udržovat mezipaměť generovaných proxyclasses, klíčovaných jejich odpovídajícími nakladači a seznamem rozhraní.Implementace by měla být opatrná, aby neodkazovala na třídy classloaders, interfaces a proxy tak, aby zabránila hromadným nakladačům a všem jejich třídám, aby byly případně shromažďovány odpadky.

vlastnosti třídy Proxy

třída proxy má následující vlastnosti:

  • třídy Proxy jsou veřejné, konečné a ne abstraktní.
  • nekvalifikovaný název třídy proxy je nespecifikován. Názvy tříd spaceof, které začínají řetězcem "$Proxy", však mají být vyhrazeny pro třídy proxy.
  • třída proxy rozšiřuje java.lang.reflect.Proxy.
  • třída proxy implementuje přesně rozhraní zadaná při jeho vytvoření ve stejném pořadí.
  • pokud třída proxy implementuje neveřejné rozhraní, bude definována ve stejném balíčku jako toto rozhraní. V opačném případě je balíček třídy proxy také nespecifikován. Všimněte si, že balení nezabrání tomu, aby třída proxy byla úspěšně definována v konkrétním balíčku za běhu,a ani třídy již definované ve stejném zavaděči třídy a ve stejném balení s konkrétními signatáři.
  • protože třída proxy implementuje všechna rozhraní zadaná při jeho vytvoření, vyvolání getInterfaces na jeho objektClass vrátí pole obsahující stejný seznam rozhraní (v pořadí uvedeném při jeho vytvoření), vyvolání getMethods na jeho objekt Class vrátí pole Method objektů, které zahrnujívšechny metody v těchto rozhraních, a vyvolánígetMethod najde metody v rozhraních proxy, jak by se dalo očekávat.
  • metoda Proxy.isProxyClass vrátí true, pokud je předána třída proxy — třída vrácenáProxy.getProxyClass nebo třída objektu vráceného Proxy.newProxyInstance — a false jinak. Spolehlivost této metody je důležitá pro schopnost ji používat k bezpečnostním rozhodnutím, takže její implementace by neměla být jen testováním, zda se dotyčná třída rozšiřujejava.lang.reflect.Proxy.
  • java.security.ProtectionDomain proxyclass je stejný jako u systémových tříd načtených zavaděčem bootstrapclass, například java.lang.Object, protože kód pro třídu proxy je generován důvěryhodným systémovým kódem. Tato ochranná doména bude obvykle udělenajava.security.AllPermission.

vytvoření instance Proxy

každá třída proxy má jeden veřejný konstruktor, který přebírá oneargument, implementaci rozhraní InvocationHandler.

každá instance proxy má přidružený objekt obsluhy vyvolání, který byl předán jeho konstruktoru. Namísto použití rozhraní reflection API pro přístup k veřejnému konstruktoru lze proxyinstance vytvořit také voláním metodyProxy.newProxyInstance, která kombinuje volání Proxy.getProxyClass s vyvoláním konstruktoru s obslužnou rutinou vyvolání.Proxy.newProxyInstance hodíIllegalArgumentException ze stejných důvodů jakoProxy.getProxyClass.

vlastnosti instance Proxy

instance proxy má následující vlastnosti:

  • vzhledem k instanci proxy proxy a jednomu z rozhraní implementovaného jeho třídou proxy Foo vrátí následující výraz true:
     proxy instanceof Foo

    a následující operace cast bude úspěšná (spíše než házení ClassCastException):

     (Foo) proxy
  • statická metoda Proxy.getInvocationHandler vrátí obslužnou rutinu vyvolání přidruženou k instanci proxy jako její argument. Pokud objekt předanýProxy.getInvocationHandler není instancí proxy, bude vyvolán IllegalArgumentException.
  • vyvolání metody rozhraní na instanci proxy bude kódováno a odesláno do metodyinvoke obsluhy vyvolání, jak je popsáno níže.

    samotná instance proxy bude předána jako první argument invoke, který je typu Object.

    druhým argumentem předaným invoke bude instancejava.lang.reflect.Method odpovídající metodě interface vyvolané na instanci proxy. Deklarující třída objektu Method bude rozhraní, ve kterém byla themethod deklarována, což může být superinterface proxyinterface, kterým třída proxy dědí metodu.

    třetím argumentem předaným invoke bude anarray objektů obsahujících hodnoty argumentů předaných metodou vyvolání na instanci proxy. Argumenty primitivetypů jsou zabaleny do instance příslušné třídy primitivewrapper, například java.lang.Integer nebojava.lang.Boolean. Implementace metodyinvoke je zdarma k úpravě obsahu thisarray.

    hodnota vrácená metodou invoke se stane návratovou hodnotou vyvolání metody na instanci proxy. Ifthe deklarovaná návratová hodnota metody interface je primitivetype, pak hodnota vrácená invoke musí být aninstance odpovídající třídy primitivního obalu; jinak musí být Typ přiřazitelný deklarovanému typu návratu. Je-li hodnota vrácená invoke null a typ návratu metody Interface je primitivní, pak bude metoda vyvolánaNullPointerException na instanci proxy. Pokud hodnota vrácenáinvoke není jinak kompatibilní s deklarovaným návratovým typem metody, jak je popsáno výše, bude proxyinstance vyvolánaClassCastException.

    pokud je výjimka vyvolána metodou invoke, bude také vyvolána metodou vyvolání na instanci proxy.Typ výjimky musí být přiřazen k některému z typů výjimek deklarovaných v podpisu metody rozhraní nebo k nekontrolovaným typům výjimekjava.lang.RuntimeException nebojava.lang.Error. Pokud je zaškrtnutá výjimka vyvolánainvoke, která není přiřazena žádnému z typů výjimek deklarovaných v klauzuli throws rozhraní interfacemethod, pak UndeclaredThrowableExceptionbude vyvolána metodou na instanci proxy. UndeclaredThrowableException bude konstruováno svýjimka, která byla vyvolána metodou invoke.

  • vyvolání metod hashCode,equals nebo toString deklarovaných v java.lang.Object na instanci proxy bude kódováno a odesláno do metody invokeobsluhy vyvolání stejným způsobem jako vyvolání metody rozhraní je kódováno a odesláno, jak je popsáno výše. Deklarovaná třída objektu Method předaného do invoke bude java.lang.Object. Jiné veřejné metody proxyinstance zděděné z java.lang.Object nejsou přepsány třídou proxy, takže vyvolání těchto metod se chová jako u instancí java.lang.Object.

metody duplikované v MultipleProxy rozhraních

pokud dvě nebo více rozhraní třídy proxy obsahují metodu se stejným názvem a podpisem parametrů, pořadí rozhraní proxyclass se stává významným. Pokud je takový duplicatemethod vyvolán na instanci proxy, objekt Methodpředaný obslužné rutině vyvolání nemusí být nutně ten, jehož deklarační třída je přiřazitelná z referenčního typu rozhraní, přes které byla vyvolána metoda proxy. Thislimitation existuje, protože odpovídající implementace metody ve generované třídě proxy nemůže určit, přes které rozhraní byla vyvolána. Proto, když je vyvolána duplicitní metoda na instanci proxy, objekt Method pro metodu v nejpřednějším rozhraní, které obsahuje metodu (buď přímo nebo zděděnou superinterface) v seznamu rozhraní třídy proxy, je předán metodě invoke vyvolávací obslužné rutiny, bez ohledu na typ odkazu, kterým došlo k vyvolání metody.

pokud rozhraní proxy obsahuje metodu se stejným názvem a podpisem parametru jako hashCode,equals nebo toString metodyjava.lang.Object, je-li taková metoda vyvolána na instanci aproxy, objekt Method předaný obslužnému programu invocation bude mít java.lang.Object jako třídu itsdeclaring. Jinými slovy, veřejné, nekončící metodyjava.lang.Object logicky předcházejí všem proxyinterfaces pro určení toho, který objekt Methodpředá obslužnému programu vyvolání.

Všimněte si také, že když je duplicitní metoda odeslána do aninvocation handler, metoda invoke může házet pouze zaškrtnuté typy výjimek, které jsou přiřazeny jednomu z typů výjimek v klauzuli throws metody ve všech rozhraních proxy, které lze vyvolat. Pokud metodainvoke vyvolá zaškrtnutou výjimku, která není přiřazena k žádnému z typů výjimek deklarovaných metodou v jednom z rozhraní proxy, přes které může být vyvolána, bude vyvoláním na instanci proxy vyvolána anunchecked UndeclaredThrowableException. Toto omezení znamená, že ne všechny typy výjimek vrácené vyvolánímgetExceptionTypes na Method objectpassed na metodu invoke lze nutně úspěšně hodit metodou invoke.

serializace

vzhledem k tomu, že java.lang.reflect.Proxy implementujejava.io.Serializable, mohou být proxy instance serializovány, jak je popsáno v této části. Pokud instance proxy obsahuje obslužnou rutinu pro vyvolání, která není přiřazenajava.io.Serializable, pak bude vyvolánajava.io.NotSerializableException, pokud je taková instance zapsána dojava.io.ObjectOutputStream. Všimněte si, že pro proxyclasses má implementace java.io.Externalizable stejný účinek s ohledem na serializaci jako implementacejava.io.Serializable: metody writeExternala readExternal rozhraníExternalizable nebudou nikdy vyvolány na instanci aproxy (nebo obslužné rutině vyvolání) jako součást itsserializačního procesu. Stejně jako u všech objektů Class je objektClass pro třídu proxy vždy serializovatelný.

třída proxy nemá žádná serializovatelná pole aserialVersionUID 0L. Jinými slovy, když je objekt Class pro třídu proxy předán statickému lookup metodějava.io.ObjectStreamClass, vrácená instanceObjectStreamClass bude mít následující vlastnosti:

  • vyvolání metody getSerialVersionUID se vrátí 0L.
  • vyvoláním metody getFields se vrátí arrayof délka nula.
  • vyvoláním metody getField s jakýmkoli argumentemString se vrátí null.

protokol streamu pro serializaci objektů podporuje typový kód s názvem TC_PROXYCLASSDESC, což je terminalsymbol v gramatice pro formát streamu; jeho typ a hodnota jsou definovány následujícím konstantním polem v rozhraníjava.io.ObjectStreamConstants :

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

gramatika také obsahuje následující dvě pravidla, prvníbýt alternativní rozšíření původního newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyclassdescinfo:
(int)<count>proxyinterfacename classAnnotationsuperClassDesc

proxyInterfaceName:
(utf)

když ObjectOutputStream serializuje classdescriptor pro třídu, která je třídou proxy, jak je určeno obcházením objektu Class metodouProxy.isProxyClass, použijeTC_PROXYCLASSDESC typový kód namístoTC_CLASSDESC podle výše uvedených pravidel. In theexpansion of proxyClassDescInfo, sekvence položek proxyinterfacename jsou názvy všech rozhraní implementovaných třídou proxy, v pořadí, v jakém jsou vráceny vyvoláním metody getInterfaces na objekt Class. Položky classAnnotation andsuperClassDesc mají stejný význam jako v pravidle eclassdescinfo. Pro třídu proxy, superClassDescis deskriptor třídy pro její superclass,java.lang.reflect.Proxy; včetně tohoto descriptorallows pro vývoj serializované reprezentace třídy Proxy pro instance proxy.

pro non-proxy třídy, ObjectOutputStream volá itsprotected annotateClass metoda umožňující podtřídy zapisovat vlastní data do proudu pro konkrétní třídu. Pro proxyclasses je místo annotateClass volána následující metoda v java.io.ObjectOutputStream s objektem Class pro třídu proxy:

 protected void annotateProxyClass(Class cl) throws IOException;

výchozí implementace annotateProxyClass vObjectOutputStream nedělá nic.

když se ObjectInputStream setká s typovým kódemTC_PROXYCLASSDESC, deserializuje classdescriptor pro třídu proxy ze streamu, formátovaného jako popsaný výše. Namísto volání metody resolveClassk vyřešení objektu Class pro classdescriptor je volána následující metoda vjava.io.ObjectInputStream :

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

seznam názvů rozhraní, které byly deserializovány v deskriptoru proxyclass, je předán jako interfaces argumentto resolveProxyClass.

výchozí implementace resolveProxyClass vObjectInputStream vrací výsledky voláníProxy.getProxyClass se seznamemClass objektů pro rozhraní pojmenovaná v parametruinterfaces. Class objectused pro každý název rozhraní i je hodnota přeladěná voláním

 Class.forName(i, false, loader)

kdeloaderje první nenulový zavaděč třídy v zásobníku execution, nebonull, pokud na zásobníku nejsou žádné nenulové zatížení třídy. Jedná se o stejnou volbu zavaděče třídy provedenouchybné chování metodyresolveClass. Toto stejné označeníloaderje také třída loader předánaProxy.getProxyClass. PokudProxy.getProxyClasshodíIllegalArgumentException,resolveClasshodíClassNotFoundExceptionobsahujícíIllegalArgumentException.

vzhledem k tomu, třída proxy nikdy nemá své vlastní serializovatelné pole, theclassdata v reprezentaci proudu proxy instanceconsistuje zcela instance dat pro jeho superclass,java.lang.reflect.Proxy. Proxy má oneserializovatelné pole, h, které obsahuje invocationhandler pro instanci proxy.

příklady

zde je jednoduchý příklad, který vytiskne zprávu před a po vyvolání metody na objekt, který implementuje libovolný seznam rozhraní:

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

vytvořit DebugProxy pro implementaci rozhraní Foo a zavolat jednu z jeho metod:

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

zde je příklad třídy obslužné obslužné rutiny, která poskytuje výchozí chování proxy pro metody zděděné zjava.lang.Object a implementuje delegování vyvolání určité metody na odlišné objekty v závislosti na rozhraní vyvolané metody:

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

podtřídy Delegator mohou přepsatinvokeNotDelegated, aby implementovaly chování vyvolání proxymethod, které nemají být přímo delegovány na jiné objekty, a mohou přepsat proxyHashCode,proxyEquals a proxyToString tooverride výchozí chování metod, které proxy zdědí z java.lang.Object.

postavit Delegator pro implementaci rozhraní Foo :

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

Všimněte si, že implementace výše uvedené třídy Delegator má být ilustrativnější než optimalizovaná; například, namísto ukládání do mezipaměti a porovnávání objektů Methodpro metody hashCode, equals atoString by je mohlo porovnat pouze podle jejich názvů řetězců, protože žádný z těchto názvů metod není přetížen vjava.lang.Object.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.