tartalom
Bevezetés
dinamikus Proxy API
szerializáció
példák
Bevezetés
a dinamikus proxyosztály olyan osztály, amely futásidőben meghatározott listof interfészeket valósít meg úgy, hogy az osztály egyik példányának egyik interfészén keresztül történő metódushívást egy másik objektumhoz egy uniforminterfészen keresztül továbbítják. Így egy dinamikus proxy osztály használható atype-safe proxy objektum létrehozására az interfészek listájához anélkül, hogy a proxy osztály előgenerálására lenne szükség, például fordítási idejű eszközökkel.A dinamikus proxy osztály egy példányán lévő metódushívások a példány invocationhandlerében egyetlen metódushoz vannak csatolva, és egyjava.lang.reflect.Method
objektummal vannak kódolva, amely azonosítja a meghívott metódust és egy Object
típusú tömböt, amely tartalmazza az argumentumokat.
a dinamikus proxyosztályok hasznosak egy olyan alkalmazás vagy könyvtár számára, amelynek biztosítania kell az interfész API-kat bemutató invokationson objektumok típusbiztonságos fényvisszaverő feladását. Például egy applicationcan egy dinamikus proxy osztály létrehozni egy objektumot, amely implementsmultiple önkényes eseményfigyelő interfészek — interfaces thatextend java.util.EventListener
— feldolgozni egy varietyof események különböző típusú egységes módon, például bylogging minden ilyen események egy fájlba.
dinamikus Proxy osztály API
a dinamikus proxy osztály (az alábbiakban egyszerűen proxyosztálynak nevezzük) egy olyan osztály, amely az osztály létrehozásakor futásidőben meghatározott interfészek listáját valósítja meg.
a proxy interfész olyan interfész, amelyproxy osztály hajtja végre.
a proxy példány egy proxy osztály példánya.
Proxy osztály létrehozása
Proxy osztályok, valamint azok példányai a java osztály statikus módszerei.lang.reflektálj.Proxy.
a Proxy.getProxyClass
metódus ajava.lang.Class
objektumot adja vissza egy classloaderrel és egy sor interfésszel rendelkező proxy osztályhoz. A proxy osztály meg lesz határozva a megadott osztály betöltőben, és implementálja az összes mellékelt interfészt. Ha az interfészek azonos permutációjához egy proxy osztály már definiálva van az osztálybetöltőben, akkor a meglévő proxy osztály visszatér; ellenkező esetben az ilyen interfészek proxy osztályát dinamikusan generálják és definiálják az osztálybetöltőben.
számos korlátozás vonatkozik azokra a paraméterekre, amelyeket Proxy.getProxyClass
:
- a
Class
tömb összesinterfaces
objektumának interfészeket kell képviselnie, nem osztályokat vagy primitív típusokat. - a
interfaces
tömb két eleme nem hivatkozhat azonosClass
objektumokra. - az összes interfésztípusnak név szerint láthatónak kell lennie a megadott osztálybetöltőn keresztül. Más szóval, a class loader
cl
és minden interfészi
esetében a következő kifejezésnek igaznak kell lennie:Class.forName(i.getName(), false, cl) == i
- minden nem nyilvános interfésznek ugyanabban a csomagban kell lennie;ellenkező esetben a proxy osztály nem tudja végrehajtani az összes interfészt, függetlenül attól, hogy melyik csomagban van meghatározva.
- a megadott interfészek bármely olyan tag metódusára, amely azonos aláírással rendelkezik:
- ha bármelyik módszer visszatérési típusa primitív típusú orvoid, akkor az összes metódusnak ugyanazzal a visszatérési típussal kell rendelkeznie.
- ellenkező esetben az egyik metódusnak olyan visszatérési típusúnak kell lennie, amely a többi metódus összes visszatérési típusához rendelhető.
- a kapott proxy osztály nem haladhatja meg a virtuális gép által előírt határértékeket. Például a virtuális gép korlátozhatja az osztályok által megvalósított interfészek számát 65535-re; ebben az esetben a
interfaces
tömb mérete nem haladhatja meg a 65535 értéket.
ha ezen korlátozások bármelyikét megsértik, azProxy.getProxyClass
IllegalArgumentException
– et dob. Ha ainterfaces
tömb argumentum vagy annak bármely elemenull
, akkor egy NullPointerException
lesz.
vegye figyelembe, hogy a megadott proxy interfészek sorrendje jelentős: két kérés egy proxy osztályra, az interfészek azonos kombinációjával, de más sorrendben két különálló proxy osztályt eredményez. A Proxy osztályokat a proxy interfészek sorrendje alapján különböztetjük meg annak érdekében, hogy deterministicmethod meghívási kódolást biztosítsunk azokban az esetekben, amikor a proxyinterfészek közül kettő vagy több azonos nevű metódussal rendelkezik.
annak érdekében, hogy ne kelljen új proxy osztályt generálni minden alkalommal, amikor a Proxy.getProxyClass
meghívásra kerül ugyanazzal az osztálybetöltővel és az interfészek listájával, a dinamikus proxy osztály API implementációjának meg kell őriznie a generált proxyosztályok gyorsítótárát, amelyet a megfelelő betöltők és interfészek listája ír le.Az implementációnak ügyelnie kell arra, hogy ne hivatkozzon az osztálybetöltőkre, az interfészekre és a proxy osztályokra oly módon, hogy megakadályozza az osztálybetöltőket és azok összes osztályát, hogy adott esetben hulladékgyűjtést végezzenek.
Proxy osztály tulajdonságai
a proxy osztály a következő tulajdonságokkal rendelkezik:
- a Proxy osztályok nyilvánosak, véglegesek és nem elvontak.
- a proxy osztály nem minősített neve nincs megadva. A
"$Proxy"
karakterlánccal kezdődő osztálynevek helyét azonban a proxy osztályok számára kell fenntartani. - a proxy osztály kiterjeszti
java.lang.reflect.Proxy
. - a proxy osztály pontosan végrehajtja a megadott interfészeket atits létrehozása, ugyanabban a sorrendben.
- ha egy proxy osztály nem nyilvános interfészt valósít meg, akkor azugyanabban a csomagban lesz meghatározva, mint az interfész. Ellenkező esetben a proxy osztály csomagja is meghatározatlan. Ne feledje, hogy a packagesealing nem akadályozza meg, hogy egy proxy osztály futásidőben sikeresen definiálható legyen egy adott csomagban, és az ugyanabban az osztálybetöltőben és az adott aláírókkal rendelkező azonos csomagban már definiált osztályok sem.
- mivel egy proxy osztály a létrehozásakor megadott összes interfészt megvalósítja, a
getInterfaces
meghívása aClass
objektumon egy tömböt ad vissza,amely az interfészek azonos listáját tartalmazza (a létrehozásakor megadott sorrendben), agetMethods
meghívása aClass
objektumonMethod
objektumok tömbjét adja vissza, amelyek tartalmazzák az összes metódust ezekben az interfészekben, azgetMethod
meghívása pedig metódusokat fog találni a proxy interfészekben, mivel várható volt. - a
Proxy.isProxyClass
metódus true értéket ad vissza, ha átad egy proxy osztályt-egy osztályt, amelyetProxy.getProxyClass
adott vissza, vagy egy objektum osztályát, amelyetProxy.newProxyInstance
adott vissza -, ellenkező esetben false értéket ad vissza. Aennek a módszernek a megbízhatósága fontos a használat képességéhezhogy biztonsági döntéseket hozzon, ezért annak végrehajtása nem csaktesztelje, hogy a kérdéses osztályjava.lang.reflect.Proxy
. - a proxyosztály
java.security.ProtectionDomain
ugyanaz, mint a bootstrapclass betöltő által betöltött rendszerosztályok, példáuljava.lang.Object
, mert a proxyosztály kódját megbízható rendszerkód generálja. Ez a védelmi tartomány általábanjava.security.AllPermission
lesz.
Proxypéldány létrehozása
minden proxyosztálynak van egy nyilvános konstruktora, amely a InvocationHandler
interfész megvalósítását veszi igénybe.
minden proxypéldányhoz tartozik egy társított meghíváskezelő objektum,amelyet átadtak a konstruktornak. Ahelyett, hogy a reflection API-t használnánk a nyilvános konstruktor eléréséhez, egy proxyinstance is létrehozható aProxy.newProxyInstance
metódus meghívásával, amely egyesíti a Proxy.getProxyClass
meghívásának műveleteit a konstruktor meghívásával egy meghíváskezelővel.Proxy.newProxyInstance
dobIllegalArgumentException
ugyanazon okok miatt, hogyProxy.getProxyClass
nem.
Proxy példány tulajdonságai
a proxy példány a következő tulajdonságokkal rendelkezik:
- ha egy proxypéldány
proxy
és aFoo
proxyosztálya által megvalósított interface-ek egyike, a következő kifejezés true értéket ad vissza:proxy instanceof Foo
és a következő cast művelet sikeres lesz (a dobás helyett
ClassCastException
):(Foo) proxy
- a statikus
Proxy.getInvocationHandler
metódus visszaadja a proxy példányhoz társított meghíváskezelőt argumentumként. Ha aProxy.getInvocationHandler
– nek átadott objektum nem proxy példány, akkor egyIllegalArgumentException
lesz dobva. - egy proxypéldányon lévő interface method meghívás kódolásra kerül, és a meghíváskezelő
invoke
metódusába kerül az alábbiak szerint.maga a proxy példány kerül átadásra a
invoke
első argumentof-ként, amelyObject
típusú.a
invoke
– nek átadott második argumentum ajava.lang.reflect.Method
példány lesz, amely megfelel a proxypéldányon meghívott interface metódusnak. AMethod
objektum deklaráló osztálya lesz az a felület, amelyben a themethod deklarálódott, amely lehet A proxyinterface szuperinterfésze, amelyen keresztül a proxy osztály örökli a metódust.a
invoke
– nek átadott harmadik argumentum olyan objektumok tömbje lesz, amelyek tartalmazzák a metódusmeghívás a proxy példányon. A primitivetypes argumentumai a megfelelő primitivewrapper osztály egyik példányába vannak csomagolva, példáuljava.lang.Integer
vagyjava.lang.Boolean
. Ainvoke
módszer megvalósítása szabadon módosíthatja ennek tartalmátarray.a
invoke
módszerrel visszaadott érték lesza metódus meghívásának visszatérési értéke a proxy példányon. Ha az interfész metódus deklarált visszatérési értéke primitivetype, akkor ainvoke
által visszaadott értéknek a megfelelő primitív wrapper osztály aninstance-jének kell lennie; ellenkező esetben a deklarált visszatérési típushoz rendelhető típusnak kell lennie. Ha ainvoke
által visszaadott értéknull
, és az Interface metódus visszatérési típusa primitív, akkor a methodinvocationNullPointerException
– ot dob a proxy példányra. Ha ainvoke
által visszaadott érték egyébként nem kompatibilis a metódus fent leírt visszatérési típusával, akkor a proxyinstanceClassCastException
értéket dob.ha a
invoke
metódus kivételt dob, akkor azt a proxy példány metódus meghívása is dobja.A kivétel típusának hozzárendelhetőnek kell lennie az interfész metodor aláírásában deklarált Exception típusok bármelyikéhez ajava.lang.RuntimeException
vagy ajava.lang.Error
nem ellenőrzött kivételtípusokhoz. Ha egy ellenőrzött kivételtinvoke
dob, amely nem rendelhető az interfacemethodthrows
záradékában deklarált exceptiontypes egyikéhez sem, akkor egyUndeclaredThrowableException
fog dobni a metódus meghívásával a proxy példányon. AUndeclaredThrowableException
ainvoke
módszerrel dobott kivétellel lesz felépítve. - a
hashCode
,equals
vagytoString
metódusok meghívása ajava.lang.Object
– ban egy proxypéldányon deklarálva lesz kódolva és elküldve a meghíváskezelőinvoke
metódusához ugyanúgy, mint az interfész metódus meghívásait kódolva és elküldve, a fent leírtak szerint. AMethod
objektum deklaráló osztályainvoke
leszjava.lang.Object
. Ajava.lang.Object
– tól örökölt proxyinstance egyéb nyilvános módszereit nem felülírja egy proxy osztály, ezért ezeknek a módszereknek a meghívásai úgy viselkednek, mint ajava.lang.Object
példányoknál.
Multipleproxy interfészekben duplikált metódusok
ha egy proxyosztály két vagy több interfésze azonos nevű és paraméteraláírású metódust tartalmaz, a proxyosztály interfészeinek sorrendje jelentőssé válik. Ha egy ilyen duplicatemethod meghívásra kerül egy proxy példányon, akkor a meghíváskezelőnek átadott Method
objektum nem feltétlenül az lesz, akinek deklaráló osztálya hozzárendelhető az interfész referenciatípusából, amelyen keresztül a proxy metódusát meghívták. Ez a korlátozás azért létezik, mert a létrehozott proxy osztályban a megfelelő metódus implementáció nem tudja meghatározni, hogy melyik interfészen keresztül hívták meg. Ezért, amikor egy duplikált metódust meghívunk egy proxy példányra, a metódusMethod
objektuma a legelső interfészen, amely tartalmazza a metódust (vagy közvetlenül vagy egy szuperinterfészen keresztül örökölt) a proxy osztály interfészlistájában, a meghíváskezelő invoke
metódusához kerül, függetlenül attól, hogy milyen referenciatípuson keresztül történt a metódus meghívása.
ha egy proxy interfész olyan metódust tartalmaz, amelynek neve ésparaméter-aláírása megegyezik a hashCode
,equals
vagy toString
metódusainakjava.lang.Object
metódusaival, amikor egy ilyen metódust meghívnak az aproxy példányon, akkor az invokációkezelőnek átadott Method
objektum java.lang.Object
lesz az itsdeclaring osztály. Más szavakkal, ajava.lang.Object
Nyilvános, nem végleges módszerei logikusan megelőzik az összes proxyinterfészt annak meghatározásához, hogy melyik Method
objektumot adja át a meghíváskezelőnek.
vegye figyelembe azt is, hogy amikor egy duplikált metódust küldünk az aninvocation handler-nek, a invoke
metódus csak olyan ellenőrzött kivételtípusokat dobhat ki, amelyek a metódus throws
záradékának egyik exceptiontípusához rendelhetők az allof proxy interfészekben, amelyeken keresztül meghívható. Ha ainvoke
metódus olyan ellenőrzött kivételt dob, amely nem rendelhető a metódus által deklarált kivételtípusokhoz az egyik proxy interfészen keresztül, amelyen keresztül meghívható, akkor az anunchecked UndeclaredThrowableException
a proxypéldány meghívásával lesz dobva. Ez a korlátozás azt jelenti, hogy agetExceptionTypes
meghívásával a Method
objektumon a invoke
metódusra átadott kivételtípusok nem feltétlenül dobhatók sikeresen a invoke
metódussal.
szerializáció
mivel a java.lang.reflect.Proxy
megvalósítja azjava.io.Serializable
– et, a proxypéldányok sorosíthatók, az ebben a szakaszban leírtak szerint. Ha azonban egy proxypéldány tartalmaz egy meghíváskezelőt, amely nem rendelhetőjava.io.Serializable
– hez, akkor egyjava.io.NotSerializableException
lesz dobva, ha egy ilyen példányjava.io.ObjectOutputStream
– re van írva. Ne feledje, hogy a proxyclasses esetében a java.io.Externalizable
implementálása ugyanolyan hatással van a szerializációra, mint ajava.io.Serializable
implementálása: a writeExternal
és readExternal
metódusok aExternalizable
interfészen soha nem lesznek meghívva az aproxy példányon (vagy egy meghíváskezelőn) a sorosítási folyamat részeként. Mint minden Class
objektum esetében, a proxy osztályClass
objektuma is alwaysserializálható.
a proxy osztálynak nincsenek sorosítható mezői, aserialVersionUID
pedig 0L
. Más szavakkal, amikor egy proxy osztály Class
objektumát átadjáka statikus lookup
módszerjava.io.ObjectStreamClass
, a visszaadottObjectStreamClass
példány a következő tulajdonságokkal rendelkezik:
- a
getSerialVersionUID
metódus Meghívásavisszatérés0L
. - a
getFields
metódus meghívása egy arrayof hosszúságú nullát ad vissza. - a
getField
metódus meghívása bármelyString
argumentummalnull
értéket ad vissza.
az objektumsorosításhoz használt stream protokoll támogatja a TC_PROXYCLASSDESC
nevű típuskódot, amely a stream formátum nyelvtanának terminális szimbóluma; típusát és értékét a következő konstans mező határozza meg ajava.io.ObjectStreamConstants
felületen:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
a nyelvtan a következő két szabályt is tartalmazza, az első az eredeti newclassdescrule alternatív kiterjesztése:
newClassDesc:TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
proxyInterfaceName classAnnotationsuperClassDesc
proxyinterfacename:(utf)
amikor egy ObjectOutputStream
szerializálja a classdescriptort egy olyan osztály számára, amely proxy osztály, a Class
objektum megkerülésével aProxy.isProxyClass
metódushoz, akkor aTC_PROXYCLASSDESC
típuskódot használja aTC_CLASSDESC
helyett, a fenti szabályokat követve. A proxyClassDescInfo kiterjesztésében a proxyinterfacename elemek sorrendje a proxy osztály által végrehajtott összes interface neve, abban a sorrendben, hogy a getInterfaces
metódus meghívásával adják vissza Class
objektum. A classAnnotation andsuperClassDesc elemeknek ugyanaz a jelentése, mint a theclassDescInfo szabályban. Proxy osztály esetén a superClassDescis a superclass osztályleírója,java.lang.reflect.Proxy
; ezzel a leíróval lehetővé teszi a Proxy
osztály sorosított ábrázolásának fejlődését proxy példányok esetén.
nem proxy osztályok esetén a ObjectOutputStream
meghívja az itsprotected annotateClass
metódust, amely lehetővé teszi, hogy az alosztályok egyéni adatokat írjanak egy adott osztály adatfolyamába. Proxyclasses esetén a annotateClass
helyett a java.io.ObjectOutputStream
– ben a következő method-t hívjuk meg a proxy osztály Class
objektumával:
protected void annotateProxyClass(Class cl) throws IOException;
a annotateProxyClass
alapértelmezett megvalósításaObjectOutputStream
– ban nem tesz semmit.
amikor egy ObjectInputStream
találkozik aTC_PROXYCLASSDESC
típuskóddal, a classdescriptor-t dezerializálja egy proxy osztály számára az adatfolyamból, a fent leírtak szerint formázva. Ahelyett, hogy meghívná a resolveClass
metódust a classdescriptor Class
objektumának feloldására, ajava.io.ObjectInputStream
következő metódusát hívjuk meg:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
a proxyclass leíróban deserializált interfésznevek listája interfaces
argumentto resolveProxyClass
néven kerül átadásra.
a resolveProxyClass
alapértelmezett implementációjaObjectInputStream
– ban aProxy.getProxyClass
meghívásának eredményét adja vissza aClass
objektumok listájával ainterfaces
paraméterben megnevezett interfészekhez. A Class
objectused for each interface namei
a
Class.forName(i, false, loader)
hívással újra hangolt érték, ahol aloader
az első nem null osztályú betöltő a végrehajtó veremben, vagynull
, ha nincs Nem null osztályú betöltő a veremben. Ez ugyanaz az osztály betöltő választás, amelyet aresolveClass
módszer alapértelmezett viselkedése tesz. Ez aloader
érték egyben aProxy.getProxyClass
– nak átadott osztálybetöltő is. HaProxy.getProxyClass
dob egyIllegalArgumentException
,resolveClass
dob egyClassNotFoundException
tartalmazóIllegalArgumentException
.
mivel a proxy osztálynak soha nincs saját sorosítható mezője, a proxy példány adatfolyam-ábrázolásában szereplő classdata teljes egészében a szuperosztály példányadataiból áll,java.lang.reflect.Proxy
. A Proxy
egy sorosítható mezővel rendelkezik, h
, amely tartalmazza a proxypéldány invocationhandler-jét.
példák
itt van egy egyszerű példa, amely kiírja az üzenetet, mielőtt andafter egy metódus invokáció egy objektum, amely végrehajtja egy önkényes interfészek listája:
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; }}
DebugProxy
létrehozása a Foo
interfész implementációjához és az egyik módszer meghívása:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
itt egy példa egy segédprogram meghívás kezelő osztály, amely biztosítja az alapértelmezett proxy viselkedés metódusok örököltjava.lang.Object
és végrehajtja delegálása certainproxy metódus meghívások különböző objektumok függően ainterfész a meghívott metódus:
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()); }}
a Delegator
alosztályai felülbírálhatják ainvokeNotDelegated
– et,hogy megvalósítsák a proxymethod meghívások viselkedését, amelyeket nem lehet közvetlenül más objektumokra delegálni, és felülbírálhatják a proxyHashCode
,proxyEquals
és proxyToString
metódusok alapértelmezett viselkedését, amelyeket a proxy a java.lang.Object
– tól örököl.
Delegator
létrehozása a Foo
interfész megvalósításához:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
vegye figyelembe, hogy a fenti Delegator
osztály megvalósítása inkább szemléltető, mint optimalizált; például a Method
objektumok gyorsítótárazása és összehasonlítása helyett a hashCode
, equals
éstoString
metódusokhoz csak a string nevük alapján illesztheti őket, mert ajava.lang.Object
– ban egyik metódus neve sem túlterhelt.