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 Object
obsahují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 loader
cl
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 objektClass
vrátí poleMethod
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éhoProxy.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říkladjava.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 proxyFoo
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ánIllegalArgumentException
. - vyvolání metody rozhraní na instanci proxy bude kódováno a odesláno do metody
invoke
obsluhy vyvolání, jak je popsáno níže.samotná instance proxy bude předána jako první argument
invoke
, který je typuObject
.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 objektuMethod
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říkladjava.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 klauzulithrows
rozhraní interfacemethod, pakUndeclaredThrowableException
bude vyvolána metodou na instanci proxy.UndeclaredThrowableException
bude konstruováno svýjimka, která byla vyvolána metodouinvoke
. - vyvolání metod
hashCode
,equals
nebotoString
deklarovaných vjava.lang.Object
na instanci proxy bude kódováno a odesláno do metodyinvoke
obsluhy 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 objektuMethod
předaného doinvoke
budejava.lang.Object
. Jiné veřejné metody proxyinstance zděděné zjava.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 Method
př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 Method
př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 writeExternal
a 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_PROXYCLASSDESC
newHandle 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 resolveClass
k 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)
kdeloader
je 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íloader
je také třída loader předánaProxy.getProxyClass
. PokudProxy.getProxyClass
hodíIllegalArgumentException
,resolveClass
hodíClassNotFoundException
obsahují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ů Method
pro 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
.