Dinamikus Proxyosztályok

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 Objecttí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 azonos Class 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 loadercl és minden interfész i 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.getProxyClassIllegalArgumentException – 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), a getMethods meghívása a Class objektumon Method 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, amelyet Proxy.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ául java.lang.Object, mert a proxyosztály kódját megbízható rendszerkód generálja. Ez a védelmi tartomány általábanjava.security.AllPermissionlesz.

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 a Foo 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 egy IllegalArgumentException 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, amely Object 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. A Method 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ául java.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 a invoke á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 a invoke által visszaadott érték null, é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.Errornem ellenőrzött kivételtípusokhoz. Ha egy ellenőrzött kivételtinvoke dob, amely nem rendelhető az interfacemethod throws záradékában deklarált exceptiontypes egyikéhez sem, akkor egy UndeclaredThrowableExceptionfog dobni a metódus meghívásával a proxy példányon. AUndeclaredThrowableExceptiona invoke módszerrel dobott kivétellel lesz felépítve.

  • a hashCode,equals vagy toString 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. A Method objektum deklaráló osztálya invoke lesz java.lang.Object. A java.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 a java.lang.Objectpé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 Methodobjektum 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 Methodobjektumot 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, aserialVersionUIDpedig 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és 0L.
  • a getFields metódus meghívása egy arrayof hosszúságú nullát ad vissza.
  • a getField metódus meghívása bármelyStringargumentummal null é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_PROXYCLASSDESCnewHandle 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 resolveClassmetó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 resolveProxyClassné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 aloaderaz 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 aresolveClassmódszer alapértelmezett viselkedése tesz. Ez aloaderérték egyben aProxy.getProxyClass– nak átadott osztálybetöltő is. HaProxy.getProxyClassdob egyIllegalArgumentException,resolveClassdob egyClassNotFoundExceptiontartalmazó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 proxyToStringmetó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 Methodobjektumok 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.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.