Clase proxy dinamice

cuprins

Introducere
API Proxy dinamic
serializare
Exemple

Introducere

o clasă proxy dinamică este o clasă care implementează o listă de interfețe specificate în timpul rulării, astfel încât o metodă invocare printr-una dintre interfețele de pe o instanță a clasei va fi codificată și expediată către un alt obiect printr-o interfață uniformă. Astfel, o clasă proxy dinamică poate fi utilizată pentru a crea un obiect proxy atype-safe pentru o listă de interfețe fără a necesita generarea prealabilă a clasei proxy, cum ar fi instrumentele de compilare.Invocările metodei pe o instanță a unei clase proxy dinamice sunt expediate către o singură metodă în invocationhandler-ul instanței și sunt codificate cu un obiectjava.lang.reflect.Method care identifică metoda care a fost invocată și o matrice de tip Objectcare conține argumentele.

clasele proxy dinamice sunt utile pentru o aplicație sau o bibliotecă care trebuie să furnizeze expedierea reflectorizantă în condiții de siguranță a invocațiilorpe obiecte care prezintă API-uri de interfață. De exemplu, o applicationcan utiliza o clasă proxy dinamic pentru a crea un obiect care implementsmultiple arbitrare event listener interfaces– interfaces thatextend java.util.EventListener— pentru a procesa o varietate de evenimente de diferite tipuri într-un mod uniform, cum ar fi bylogging toate aceste evenimente într-un fișier.

Dynamic Proxy Class API

o clasă proxy dinamică (denumită pur și simplu o clasă proxyclass de mai jos) este o clasă care implementează o listă de interfațespecificate în timpul rulării când este creată clasa.

o interfață proxy este o astfel de interfață care esteimplementată de o clasă proxy.

o instanță proxy este o instanță a unei clase proxyclass.

crearea unei clase Proxy

clasele Proxy, precum și instanțele acestora, sunt create folosindmetodele statice ale clasei java.lang.reflectă.Proxy.

metodaProxy.getProxyClass returnează obiectul java.lang.Class pentru o clasă proxy dat un classloader și o serie de interfețe. Clasa proxy va fi definităîn încărcătorul de clasă specificat și va implementa toate interfețele furnizate. Dacă o clasă proxy pentru aceeași permutare a interfețelor a fost deja definită în încărcătorul clasei, atunci clasa proxy existentă va fi returnată; în caz contrar, o clasă proxy pentru aceste interfețe va fi generată dinamic și definită în încărcătorul clasei.

există mai multe restricții privind parametrii care pot fitrecut la Proxy.getProxyClass:

  • toate obiectele Class din matriceainterfaces trebuie să reprezinte interfețe, notclasses sau tipuri primitive.
  • nu există două elemente în matricea interfaces se pot referi la obiecte identice Class.
  • toate tipurile de interfață trebuie să fie vizibile după nume prin încărcătorul de clasă specificat. Cu alte cuvinte, pentru încărcătorul de clasăcl și fiecare interfață i, următoarea expresie trebuie să fie adevărată:
     Class.forName(i.getName(), false, cl) == i
  • toate interfețele non-publice trebuie să fie în același pachet;în caz contrar, nu ar fi posibil ca clasa proxy să implementeze toate interfețele, indiferent de pachetul în care este definit.
  • pentru orice set de metode membre ale interfețelor specificate careau aceeași semnătură:
    • dacă tipul de returnare al oricăreia dintre metode este un tip primitiv orvoid, atunci toate metodele trebuie să aibă același tip de returnare.
    • în caz contrar, una dintre metode trebuie să aibă un tip de returnare care să poată fi atribuit tuturor tipurilor de returnare ale celorlalte metode.
  • clasa proxy rezultată nu trebuie să depășească limitele impuseclase de către mașina virtuală. De exemplu, VM poate limita numărul de interfețe pe care o clasă le poate implementa la 65535; în acest caz, dimensiunea matricei interfaces nu trebuie să depășească 65535.

dacă oricare dintre aceste restricții sunt încălcate,Proxy.getProxyClassva arunca un IllegalArgumentException. Dacă argumentul matriceiinterfaces sau oricare dintre elementele sale sunt null, va fi un NullPointerException.

rețineți că ordinea interfețelor proxy specificate este semnificativă: două solicitări pentru o clasă proxy cu aceeași combinație de interfețe, dar într-o ordine diferită, vor rezulta în două clase proxy distincte. Clasele Proxy se disting prin theorder interfețelor lor proxy pentru a furniza codificarea invocării metodei deterministe în cazurile în care două sau mai multe dintre interfețele proxy împărtășesc o metodă cu același nume și parametrisemnatura; acest raționament este descris mai detaliat însecțiunea de mai jos intitulată metode duplicate înmultiple interfețe Proxy.

astfel încât o nouă clasă proxy nu trebuie să fie generate eachtime Proxy.getProxyClass este invocat cu încărcător sameclass și lista de interfețe, punerea în aplicare a THEDYNAMIC proxy class API ar trebui să păstreze o memorie cache de proxyclasses generate, tastate de incarcator lor corespunzătoare și lista de interfață.Punerea în aplicare ar trebui să fie atent să nu se refere la classloaders, interfețe, și clase proxy în așa fel încât să împiedice Încărcătoare de clasă, și toate clasele lor, de la a fi gunoierecolected atunci când este cazul.

Proprietăți de clasă Proxy

o clasă proxy are următoarele proprietăți:

  • clasele Proxy sunt publice, finale și nu abstracte.
  • numele necalificat al unei clase proxy este nespecificat. Spaceof nume de clase care încep cu șirul "$Proxy" este,cu toate acestea, să fie rezervat pentru clasele proxy.
  • o clasă proxy se extinde java.lang.reflect.Proxy.
  • o clasă proxy implementează exact interfețele specificate lacrearea sa, în aceeași ordine.
  • dacă o clasă proxy implementează o interfață non-publică, atunci aceasta va fi definită în același pachet ca acea interfață. În caz contrar, pachetul unei clase proxy este, de asemenea, nespecificat. Rețineți că packagesealing nu va împiedica o clasă proxy să fie definită cu succes într-un anumit pachet în timpul rulării și nici clasele deja definite în același încărcător de clasă și același pachet cu anumiți semnatari.
  • deoarece o clasă proxy implementează toate interfețele specificate la crearea sa, invocând getInterfaces pe obiectul săuClass va returna o matrice care conține aceeași listă de interfețe (în ordinea specificată la crearea sa),invocând getMethods pe obiectul său Class va returna o matrice de obiecte Method care includ toate metodele din acele interfețe și invocândgetMethod vor găsi metode în interfețele proxy fi de așteptat.
  • metodaProxy.isProxyClass va returna true dacă este trecută o clasă proxy-o clasă returnată de Proxy.getProxyClass sau clasa unui obiect returnedby Proxy.newProxyInstance – și false altfel. Fiabilitatea acestei metode este importantă pentru capacitatea de a o utilizapentru a lua decizii de securitate, astfel încât punerea sa în aplicare nu ar trebui să fie doartest dacă clasa în cauză se extindejava.lang.reflect.Proxy.
  • java.security.ProtectionDomain a unei clase proxyclass este aceeași cu cea a claselor de sistem încărcate de încărcătorul bootstrapclass, cum ar fi java.lang.Object, deoarece codul pentru o clasă proxy este generat de codul de sistem de încredere. Acest domeniu de protecție va fi de obicei acordatjava.security.AllPermission.

crearea unei instanțe Proxy

fiecare clasă proxy are un constructor public care preia oneargument, o implementare a interfeței InvocationHandler.

fiecare instanță proxy are un obiect handler de invocare asociat,cel care a fost transmis constructorului său. În loc să utilizeze API-ul de reflecție pentru a accesa constructorul public, un proxy poate fi creat și apelând metodaProxy.newProxyInstance, care combină acțiunile de apelare Proxy.getProxyClass cu invocarea constructorului cu un handler de invocare.Proxy.newProxyInstance aruncăIllegalArgumentException din aceleași motive ca șiProxy.getProxyClass.

proprietăți instanță Proxy

o instanță proxy are următoarele proprietăți:

  • având în vedere o instanță proxy proxy și una dintre interfețele implementate de clasa proxy Foo, următoarea expresie va reveni true:
     proxy instanceof Foo

    și următoarea operație cast va reuși (mai degrabă decât throwinga ClassCastException):

     (Foo) proxy
  • static Proxy.getInvocationHandler metoda willreturn handler invocare asociate cu instancepassed proxy ca argumentul său. Dacă obiectul trecut la Proxy.getInvocationHandler nu este o instanță proxy,atunci va fi aruncat un IllegalArgumentException.
  • o invocare a metodei de interfață pe o instanță proxy va fi codificată și expediată la metodainvoke a handlerului de invocare, așa cum este descris mai jos.

    instanța proxy în sine va fi trecut ca primul argumentof invoke, care este de tip Object.

    al doilea argument transmis la invoke va fi instanțajava.lang.reflect.Method corespunzătoare metodei interface invocate pe instanța proxy. Clasa de declarare a obiectului Method va fi interfața în care a fost declarată themethod, care poate fi o superinterfață a proxyinterface prin care clasa proxy moștenește metoda.

    al treilea argument transmis la invoke va fi anarray de obiecte care conțin valorile argumentelor transmise în invocarea metodei pe instanța proxy. Argumentele primitivetypes sunt înfășurate într-o instanță a clasei primitivewrapper corespunzătoare, cum ar fi java.lang.Integer saujava.lang.Boolean. Punerea în aplicare a metodei invoke este liber de a modifica conținutul thisarray.

    valoarea returnată de metoda invoke va devinevaloarea returnată a invocării metodei pe instanța proxy. Dacăvaloarea returnată declarată a metodei de interfață este un tip primitiv, atunci valoarea returnată de invoke trebuie să fie oinstanță a clasei de înveliș primitiv corespunzătoare; în caz contrar,trebuie să fie un tip care poate fi atribuit tipului de returnare declarat. Dacă valoarea returnată de invoke este null și tipul de returnare al metodei Interface este primitiv, atunci oNullPointerException va fi aruncată de methodinvocation pe instanța proxy. Dacă valoarea returnată deinvoke nu este compatibilă cu tipul de returnare declarat al metodei, așa cum este descris mai sus, unClassCastException va fi aruncat de către proxyinstance.

    dacă o excepție este aruncată prin metoda invoke, aceasta va fi aruncată și prin invocarea metodei pe instanța proxy.Tipul excepției trebuie să poată fi atribuit oricăruia dintre tipurile de excepție declarate în semnătura metodei interfeței sau tipurilor de excepție necontrolatejava.lang.RuntimeException saujava.lang.Error. Dacă o excepție bifată este aruncată deinvoke care nu poate fi atribuită niciunui tip de excepție declarat în clauza throwsa interfacemethod, atunci un UndeclaredThrowableException va fi aruncat prin metoda invocare pe instanța proxy. UndeclaredThrowableException va fi construit cu excepția care a fost aruncată prin metoda invoke.

  • o invocare a metodelorhashCode, equals sautoString declarate în java.lang.Object pe o instanță proxy va fi codificată și expediată la metoda invokea handler-ului de invocare în același mod ca și invocările metodei de interfață sunt codificate și expediate, așa cum este descris mai sus. Clasa de declarare a obiectului Method trecut la invoke va fi java.lang.Object. Alte metode publice ale unei proxyinstance moștenite de la java.lang.Objectnu sunt suprascrise de o clasă proxy, astfel încât invocările acestor metode se comportă ca acestea pentru instanțe de java.lang.Object.

metode duplicate în Multipleproxy Interfaces

când două sau mai multe interfețe ale unei clase proxy conțin o metodă cu același nume și semnătura parametrului, ordinea interfețelor proxyclass devine semnificativă. Atunci când o astfel de metodă duplicatemethod este invocată pe o instanță proxy, obiectul Methodtransmis manipulatorului de invocare nu va fi neapărat cel a cărui clasă de declarare este atribuibilă din tipul de referință al interfeței prin care a fost invocată metoda proxy-ului. Thislimitation există deoarece metoda corespunzătoare implementationin clasa proxy generat nu poate determina care interfață itwas invocat prin. Prin urmare, atunci când o metodă duplicat este invocat pe o instanță proxy, Method obiect pentru methodin interfața rând care conține metoda (fie directly sau moștenit printr-o superinterface) în lista de interfaces clasa proxy este trecut la handler invocareinvoke metoda, indiferent de tipul de referințăprin care a avut loc invocarea metodei.

dacă o interfață proxy conține o metodă cu același nume și semnătură parametrică ca metodele hashCode,equals sau toString alejava.lang.Object, atunci când o astfel de metodă este invocată pe o instanță apropiată, obiectul Method trecut la handlerul de localizare va avea java.lang.Object ca clasă de declarare. Cu alte cuvinte, metodele publice, non-finale alejava.lang.Object preced în mod logic toate interfețele proxy pentru determinarea cărora Methodobiect să treacă la handler-ul de invocare.

rețineți, de asemenea, că atunci când o metodă duplicat este expediat la handler aninvocation, invoke metoda poate throwchecked numai tipuri de excepție, care sunt atribuite la unul dintre exceptiontypes în throws clauza metodei în allof interfețele proxy care poate fi invocată prin. Dacă metoda invoke aruncă o excepție bifată care nu poate fi atribuită niciunuia dintre tipurile de excepții declarate de metodă în una dintre interfețele proxy prin care poate fi invocată, atunci UndeclaredThrowableException bifat va fi aruncat de invocarea instanței proxy. Această restricție înseamnă că nu toate tipurile de excepții returnate prin invocareagetExceptionTypes pe obiectul Method trecut la metoda invoke pot fi neapărat aruncate cu succes prin metoda invoke.

serializare

deoarecejava.lang.reflect.Proxy implementează java.io.Serializable, instanțele proxy pot fi serializate, așa cum este descris în această secțiune. Dacă un proxy instanceconține un handler de invocare care nu poate fi atribuitjava.io.Serializable, totuși, atunci unjava.io.NotSerializableException va fi aruncat dacă o astfel de instanță este scrisă unuijava.io.ObjectOutputStream. Rețineți că pentru proxyclasses, implementarea java.io.Externalizable are același efect în ceea ce privește serializarea ca implementareajava.io.Serializable: metodele writeExternalși readExternal ale interfețeiExternalizable nu vor fi niciodată invocate pe o instanță apropiată (sau un handler de invocare) ca parte a procesului său de serializare. Ca și în cazul tuturor obiectelor Class, obiectulClass pentru o clasă proxy este întotdeaunaserializabil.

o clasă proxy nu are câmpuri serializabile și unserialVersionUIDde 0L. Cu alte cuvinte, atunci când obiectul Class pentru o clasă proxy este trecut la metoda statică lookup a java.io.ObjectStreamClass, instanța returnată ObjectStreamClass va avea următoarele proprietăți:

  • invocarea metodei sale getSerialVersionUID va reveni 0L.
  • invocarea metodei sale getFields va returna o lungime arrayof zero.
  • invocarea metodei salegetField cu orice argument String va reveni null.

protocolul stream pentru serializarea obiectelor acceptă un cod de tip numit TC_PROXYCLASSDESC , care este un terminalsymbol în gramatica formatului stream; tipul și valoarea sa sunt definite de următorul câmp constant în interfațajava.io.ObjectStreamConstants :

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

gramatica include, de asemenea, următoarele două reguli, primulfiind o extindere alternativă a originalului newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count>proxyinterfacename classAnnotationsuperClassDesc

proxyinterfacename:
(utf)

când un ObjectOutputStream serializează classdescriptor pentru o clasă care este o clasă proxy, așa cum este determinat ocolind obiectul său Class la metodaProxy.isProxyClass, folosește codul de tipTC_PROXYCLASSDESC în loc deTC_CLASSDESC, urmând regulile de mai sus. Înexpansiunea proxyClassDescInfo, secvența de elemente de nume proxyinterfacename sunt numele tuturor interfețelor implementate de clasa proxy, în ordinea în care acestea sunt returnate prin invocarea metodei getInterfaces pe obiectul Class. Elementele classAnnotation anduperclassdesc au același înțeles ca și în regula classdescinfo. Pentru o clasă proxy, superClassDescis descriptorul clasei pentru superclasa sa,java.lang.reflect.Proxy; inclusiv acest descriptorallows pentru evoluția reprezentării serializate a clasei Proxy pentru instanțele proxy.

pentru clasele non-proxy, ObjectOutputStream apelează metoda itsprotected annotateClass pentru a permite subclaselor să scrie date personalizate în flux pentru o anumită clasă. Pentru proxiclase, în loc de annotateClass, metoda următoare în java.io.ObjectOutputStream se numește cu Class obiect pentru clasa proxy:

 protected void annotateProxyClass(Class cl) throws IOException;

implementarea implicită a annotateProxyClass înObjectOutputStream nu face nimic.

când un ObjectInputStream întâlnește codul de tipTC_PROXYCLASSDESC, acesta deserializează classdescriptor pentru o clasă proxy din flux, formatat așa cum este descris mai sus. În loc de asteptare sale resolveClass metoda pentru a rezolva Class obiect pentru classdescriptor, următoarea metodă înjava.io.ObjectInputStream se numește:

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

lista de nume de interfață care au fost deserializate în descriptor proxyclass sunt trecute ca interfaces argumentla resolveProxyClass.

implementarea implicită aresolveProxyClass înObjectInputStream returnează rezultatele apelăriiProxy.getProxyClass cu lista de obiecteClass pentru interfețele denumite în parametrul interfaces. Obiectul Class utilizat pentru fiecare nume de interfațăi este valoarea reglată prin apelarea

 Class.forName(i, false, loader)

undeloadereste primul încărcător de clasă non-null din stiva de execuție saunulldacă nu există încărcătoare de clasă non-null pe stivă. Aceasta este aceeași alegere a încărcătorului de clasă făcută decomportamentul implicit al metodeiresolveClass. Această valoare aloadereste, de asemenea, încărcătorul de clasă trecut laProxy.getProxyClass. DacăProxy.getProxyClassaruncă unIllegalArgumentException,resolveClassva arunca unClassNotFoundExceptioncare conțineIllegalArgumentException.

deoarece o clasă proxy nu are niciodată propriile câmpuri serializabile, datele de clasă din reprezentarea în flux a unei instanțe proxy constau în întregime din datele instanței pentru superclasa sa,java.lang.reflect.Proxy. Proxy are un câmp serializabil, h, care conține invocationhandler pentru instanța proxy.

Exemple

Iată un exemplu simplu care imprimă un mesaj înainte și după o invocare a metodei pe un obiect care implementează o listă arbitrară de interfețe:

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

pentru a construi o DebugProxy pentru o implementare a interfeței Foo și a apela una dintre metodele sale:

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

aici este un exemplu de o clasă de utilitate invocare handler thatprovides comportament proxy implicit pentru metodele moștenite de lajava.lang.Object și implementează delegarea certainproxy metoda invocări la obiecte distincte în funcție de theinterface a metodei invocate:

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

subclasele Delegator pot suprascrieinvokeNotDelegatedpentru a implementa comportamentul invocărilor proxymethod care nu trebuie delegate direct altor obiecte și pot suprascrie proxyHashCode,proxyEquals și proxyToStringpentru a suprascrie comportamentul implicit al metodelor moștenite de proxy java.lang.Object.

pentru a construi o Delegator pentru o implementare a interfeței Foo :

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

rețineți că punerea în aplicare a Delegator classgiven de mai sus este destinat să fie mai ilustrativ decât optimizat; de exemplu, în loc să memoreze în cache și să compare obiectele Method pentru metodele hashCode, equals șitoString, le-ar putea potrivi doar după numele lor de șir, deoarece niciunul dintre aceste nume de metode nu este supraîncărcat înjava.lang.Object.

Lasă un răspuns

Adresa ta de email nu va fi publicată.