Dynamiczne klasy Proxy

spis treści

wprowadzenie
dynamiczne API Proxy
serializacja
przykłady

wprowadzenie

dynamiczna Klasa proxy jest klasą, która implementuje interfejsy listof określone w czasie wykonywania, tak że wywołanie metody przez jeden z interfejsów na instancji klasy zostanie zakodowane i wysłane do innego obiektu za pomocą jednorodnego interfejsu. Tak więc, dynamiczna Klasa proxy może być użyta do stworzenia bezpiecznego obiektu proxy dla listy interfejsów bez konieczności wcześniejszego generowania klasy proxy, na przykład przy użyciu narzędzi kompilacyjnych.Wywołania metod na instancji dynamicznej klasy proxy są przypisane do pojedynczej metody w wywołaniu instancji i są kodowane z obiektemjava.lang.reflect.Method identyfikującym metodę, która została wywołana i tablicą typu Object zawierającą argumenty.

dynamiczne klasy proxy są użyteczne dla aplikacji lub bibliotek, które muszą zapewnić bezpieczne dla typu refleksyjne wysyłanie wywołań obiektów prezentujących interfejsy API. Na przykład, aplikacja może użyć dynamicznej klasy proxy do utworzenia obiektu, który implementuje wiele dowolnych interfejsów detektora zdarzeń– interfejsy, które rozszerzają java.util.EventListener — do przetwarzania różnych zdarzeń różnych typów w jednolity sposób, na przykład poprzez zapisywanie wszystkich takich zdarzeń do pliku.

dynamiczna Klasa Proxy API

dynamiczna Klasa proxy (zwana po prostu proxyclass poniżej) jest klasą, która implementuje listę interfejsów zdefiniowanych w czasie wykonywania, gdy klasa jest tworzona.

interfejs proxy to taki interfejs, który jest implementowany przez klasę proxy.

instancja proxy jest instancją proxyclass.

Tworzenie klasy Proxy

klasy Proxy, jak również ich instancje, są tworzone przy użyciu statycznych metod klasy java.lang.zastanów się.Proxy.

metoda Proxy.getProxyClass zwraca obiektjava.lang.Class dla klasy proxy z podanym classloaderem i tablicą interfejsów. Klasa proxy zostanie zdefiniowana w podanym ładowaczu klas i zaimplementuje wszystkie dostarczone interfejsy. Jeśli Klasa proxy dla tej samej permutacji interfaces została już zdefiniowana w class loader, to istniejąca Klasa proxy zostanie zwrócona; w przeciwnym razie Klasa proxy dla tych interfejsów zostanie wygenerowana dynamicznie i zdefiniowana w class loader.

istnieje kilka ograniczeń co do parametrów, które można przypisać do Proxy.getProxyClass:

  • wszystkie obiekty Class w tablicyinterfaces muszą reprezentować interfejsy, a nie Klasy lub typy prymitywne.
  • żadne dwa elementy w tablicy interfaces nie mogą odnosić się do identycznych obiektów Class.
  • Wszystkie typy interfejsów muszą być widoczne po nazwie przez specificated class loader. Innymi słowy, dla class loadercl i każdego interfejsu i następujące wyrażenie musi być prawdziwe:
     Class.forName(i.getName(), false, cl) == i
  • wszystkie Niepubliczne interfejsy muszą znajdować się w tym samym pakiecie;w przeciwnym razie Klasa proxy nie byłaby możliwa do wdrożenia wszystkich interfejsów, niezależnie od tego, w jakim pakiecie jest zdefiniowana.
  • dla dowolnego zestawu metod członkowskich podanych interfejsów, które mają tę samą sygnaturę:
    • jeśli typ zwracany przez którąkolwiek z metod jest typem prymitywnym orvoid, to wszystkie metody muszą mieć ten sam typ zwracany.
    • w przeciwnym razie jedna z metod musi mieć typ zwracany, który można przypisać do wszystkich typów zwracanych przez pozostałe metody.
  • wynikowa Klasa proxy nie może przekraczać żadnych limitów nałożonych na klasy przez maszynę wirtualną. Na przykład, VM może ograniczyć liczbę interfejsów, które klasa może zaimplementować do 65535; w tym przypadku rozmiar tablicy interfaces nie może przekraczać 65535.

jeśli którekolwiek z tych ograniczeń zostanie naruszone,Proxy.getProxyClass rzuciIllegalArgumentException. Jeśli argument tablicy interfaces lub którykolwiek z jego elementów jestnull, to NullPointerException zostanie usunięty.

zauważ, że kolejność podanych interfejsów proxy jest znacząca: dwa żądania dla klasy proxy z tym samym połączeniem interfejsów, ale w innej kolejności spowodują dwie różne klasy proxy. Klasy Proxy są rozróżniane przez teoretyka ich interfejsów proxy w celu zapewnienia deterministycznego kodowania wywołania w przypadkach, gdy dwa lub więcej z proxyinterfaces dzielą metodę o tej samej nazwie i parametrachsignature; to rozumowanie jest opisane bardziej szczegółowo w sekcji poniżej zatytułowanej metody powielane w wielu interfejsach Proxy.

aby nowa klasa proxy nie musiała być generowana za każdym razem, gdy Proxy.getProxyClass jest wywoływany z tym samym loaderem klasy i listą interfejsów, implementacja dynamicznego API klasy proxy powinna przechowywać pamięć podręczną wygenerowanych proxyclass, kluczowaną przez odpowiednie loadery i listę interfejsów.Implementacja powinna uważać, aby nie odwoływać się do klas ładujących, interfejsów i klas proxy w taki sposób, aby zapobiec pobieraniu klas ładujących i wszystkich ich klas, w stosownych przypadkach.

właściwości klasy Proxy

Klasa proxy ma następujące właściwości:

  • klasy Proxy są publiczne, ostateczne, a nie abstrakcyjne.
  • niewykwalifikowana nazwa klasy proxy jest nieokreślona. Spacja nazw klas zaczynających się od łańcucha "$Proxy" jest jednak zarezerwowana dla klas proxy.
  • Klasa proxy rozszerza się java.lang.reflect.Proxy.
  • Klasa proxy implementuje dokładnie interfejsy określone w tworzeniu atits, w tej samej kolejności.
  • jeśli Klasa proxy implementuje interfejs Niepubliczny, to zostanie zdefiniowana w tym samym pakiecie co ten interfejs. W przeciwnym razie pakiet klasy proxy jest również nieokreślony. Zauważ, że packagesealing nie uniemożliwi pomyślnego zdefiniowania klasy proxy w danym pakiecie w czasie wykonywania, a także nie będzie klas już zdefiniowanych w tym samym ładowaczu klas i tym samym pakiecie z określonymi sygnatariuszami.
  • ponieważ Klasa proxy implementuje wszystkie interfejsy określone podczas jej tworzenia, wywołanie getInterfaces na swoim obiekcieClass zwróci tablicę zawierającą taką samą listę interfejsów (w kolejności podanej przy jego tworzeniu),wywołanie getMethods na swoim obiekcie Class zwróci tablicę obiektów Method, które zawierają wszystkie metody w tych interfejsach, a wywołaniegetMethod znajdzie metody w interfejsach proxy zgodnie z oczekiwaniami.
  • metoda Proxy.isProxyClass zwróci true, jeśli zostanie przekazana Klasa proxy– Klasa zwrócona przezProxy.getProxyClass lub klasa obiektu zwrócona przez Proxy.newProxyInstance— i false w przeciwnym razie. Niezawodność tej metody jest ważna dla możliwości jej wykorzystania do podejmowania decyzji dotyczących bezpieczeństwa, więc jej implementacja nie powinna sprawdzać tylko, czy dana klasa rozciąga sięjava.lang.reflect.Proxy.
  • java.security.ProtectionDomain klasy systemowe ładowane przez bootstrapclass loader, takie jak java.lang.Object, ponieważ Kod klasy proxy jest generowany przez zaufany kod systemu. Ta domena ochrony będzie zazwyczaj przyznawanajava.security.AllPermission.

Tworzenie instancji Proxy

każda klasa proxy ma jeden publiczny konstruktor, który przyjmuje oneargument, implementację interfejsu InvocationHandler.

każda instancja proxy ma powiązany obiekt obsługi wywołania,ten, który został przekazany do jej konstruktora. Zamiast używać API reflection, aby uzyskać dostęp do konstruktora publicznego, można również utworzyć proxyinstance poprzez wywołanie metodyProxy.newProxyInstance, która łączy działania wywołania Proxy.getProxyClass z wywołaniem konstruktora z obsługą wywołania.Proxy.newProxyInstance rzuca IllegalArgumentException z tych samych powodów coProxy.getProxyClass.

właściwości instancji Proxy

instancja proxy ma następujące właściwości:

  • biorąc pod uwagę instancję proxy proxy i jeden z interfejsów zaimplementowanych przez jej klasę proxy Foo, następne wyrażenie zwróci true:
     proxy instanceof Foo

    i następująca operacja cast zakończy się sukcesem (zamiast throwinga ClassCastException):

     (Foo) proxy
  • statyczna metoda Proxy.getInvocationHandlerzwróci jako argument funkcję wywołania powiązaną z instancją proxy. Jeśli obiekt przekazany doProxy.getInvocationHandler nie jest instancją proxy,wtedy zostanie wyrzucony obiekt IllegalArgumentException.
  • wywołanie metody interfejsu na instancji proxy zostanie zakodowane i wysłane do metody obsługi wywołaniainvoke, jak opisano poniżej.

    sama instancja proxy zostanie przekazana jako pierwszy argument invoke, który jest typu Object.

    drugim argumentem przekazanym do invoke będzie instancjajava.lang.reflect.Method odpowiadająca metodzie interface wywołanej na instancji proxy. Klasa deklarująca obiekt Method będzie interfejsem, w którym zadeklarowano thethod, który może być superinterface ’ em interfejsu proxy, przez który Klasa proxy dziedziczy metodę.

    trzeci argument przekazany do invoke będzie trajektorią obiektów zawierających wartości argumentów przekazanych w wywołaniu metody na instancji proxy. Argumenty typu primitivetypes są zawinięte w instancję odpowiedniej klasy primitivewrapper, takiej jak java.lang.Integer lubjava.lang.Boolean. Implementacja metodyinvoke może dowolnie modyfikować zawartość tej metody.

    wartość zwrócona przez metodę invoke stanie się wartością zwracaną wywołania metody na instancji proxy. Jeżeli zadeklarowaną wartością zwracaną metody interfejsu jest primitivetype, wtedy wartość zwracana przez invoke musi być instancją odpowiedniej klasy primitive wrapper; w przeciwnym razie musi być typem przypisywalnym do zadeklarowanego typu return. Jeżeli wartość zwracana przez invoke wynosi null, A typ zwracany przez metodę Interface jest prymitywny, wtedy metoda invocation zwróciNullPointerException na instancję proxy. Jeśli wartość zwracana przezinvoke nie jest zgodna z typem zwracanym przez metodę, jak opisano powyżej, proxyinstance wyrzuciClassCastException.

    jeśli wyjątek zostanie zgłoszony przez metodę invoke, zostanie również zgłoszony przez wywołanie metody na instancji proxy.Typ wyjątku musi być przypisany do któregokolwiek z typów wyjątków zadeklarowanych w podpisie metody interfejsu lub do niezaznaczonych typów wyjątkówjava.lang.RuntimeExceptionlubjava.lang.Error. Jeśli wyjątek checked zostanie zgłoszony przezinvoke, który nie jest przypisany do żadnego z typów wyjątków zadeklarowanych w throws klauzuli interfacemethod, wtedy wywołanie metody na instancji proxy spowoduje wywołanie wyjątku UndeclaredThrowableException. UndeclaredThrowableException zostanie skonstruowany z wyjątkiem, który został wyrzucony przez metodę invoke.

  • wywołanie metod hashCode,equals lub toString zadeklarowanych wjava.lang.Object na instancji proxy zostanie zakodowane i wysłane do metody invokeobsługi wywołania w taki sam sposób, jak wywołania metody interfejsu są kodowane i wysyłane, jak opisano powyżej. Klasa deklarująca obiekt Method przekazany do invoke będzie miała wartość java.lang.Object. Inne publiczne metody proxyinstance dziedziczone z java.lang.Object nie są obsługiwane przez klasę proxy, więc wywołania tych metod zachowują się tak samo jak dla instancji java.lang.Object.

metody zduplikowane w interfejsach MultipleProxy

gdy dwa lub więcej interfejsów klasy proxy zawierają metodę o tej samej nazwie i podpisie parametru, kolejność interfejsów proxyclass staje się znacząca. Gdy taki duplicatemethod jest wywoływany na instancji proxy, obiekt Methodprzekazany do obsługi wywołania niekoniecznie będzie tym, którego Klasa deklarowania jest przypisywana z referencyjnego typu interfejsu, przez który została wywołana metoda proxy. Ograniczenie to istnieje, ponieważ odpowiednia implementacja metody w Wygenerowanej klasie proxy nie może określić, przez który interfejs została wywołana. W związku z tym, gdy wywołana jest duplikat metody w instancji proxy, obiekt Method dla metody w czołowym interfejsie, który zawiera metodę (zarówno bezpośrednio, jak i dziedziczoną przez superinterface) z listy interfejsów klasy proxy, jest przekazywany do metodyinvoke obsługi wywołania, niezależnie od rodzaju odniesienia, przez który nastąpiło wywołanie metody.

jeśli interfejs proxy zawiera metodę o tej samej nazwie i sygnaturze parametru co metody hashCode,equals lub toString zjava.lang.Object, gdy taka metoda jest wywoływana na instancji aproxy, obiekt Method przekazany do obsługi invocation będzie miał java.lang.Object jako klasę deklarującą. Innymi słowy, publiczne, niekończące się metody java.lang.Objectlogicznie poprzedzają wszystkie proxyinterfaces w celu określenia, który obiekt Method ma zostać przekazany do obsługi wywołania.

zauważ również, że gdy duplikat metody jest wysyłany do procedury obsługi invocation, metoda invoke może rzucać tylko typy wyjątków, które można przypisać do jednego z typów wyjątków w klauzuli throws metody we wszystkich interfejsach proxy, przez które można ją wywołać. Jeśli metoda invoke wyrzuci wyjątek checked, który nie jest przypisywalny do żadnego z typów wyjątków zadeklarowanych przez metodę w jednym z interfejsów proxy, przez które może być wywołana, to anunchecked UndeclaredThrowableException zostanie odrzucony przez wywołanie na instancji proxy. Ograniczenie to oznacza, że nie wszystkie typy WYJĄTKÓW zwracane przez wywołaniegetExceptionTypes dla obiektu Method przypisanego do metody invoke mogą koniecznie zostać pomyślnie odrzucone przez metodę invoke.

serializacja

ponieważ java.lang.reflect.Proxy implementujejava.io.Serializable, instancje proxy mogą być beserializowane, jak opisano w tej sekcji. Jeśli instancja proxy zawiera funkcję obsługi wywołania, której nie można przypisać dojava.io.Serializable, wtedy zostanie wyrzuconajava.io.NotSerializableException, jeśli taka instancja zostanie zapisana dojava.io.ObjectOutputStream. Zauważ, że w przypadku proxyclass implementacja java.io.Externalizable ma taki sam efekt w odniesieniu do serializacji, jak implementacjajava.io.Serializable: metody writeExternali readExternal interfejsuExternalizable nigdy nie będą wywoływane w instancji aproxy (lub procedurze obsługi wywołania) jako część procesu itsserialization. Podobnie jak w przypadku wszystkich obiektów Class, obiektClass dla klasy proxy jest zawsze możliwy do zrealizowania.

Klasa proxy nie ma serializowalnych pól iserialVersionUID0L. Innymi słowy, gdy obiekt Class dla klasy proxy jest przekazywany do statycznej metodylookup java.io.ObjectStreamClass, zwracana instancjaObjectStreamClass będzie miała następujące właściwości:

  • wywołanie jej metody getSerialVersionUID spowoduje przywrócenie 0L.
  • wywołanie metody getFields zwróci zero długości tablicy.
  • wywołanie metodygetField dowolnym argumentem String zwróci null.

protokół stream dla serializacji obiektów obsługuje kod typu o nazwie TC_PROXYCLASSDESC, który jest terminalsymbol w gramatyce dla formatu stream; jego typ i wartość są zdefiniowane przez następujące pole stałe w interfejsiejava.io.ObjectStreamConstants :

 final static byte TC_PROXYCLASSDESC = (byte)0x7D;

gramatyka zawiera również następujące dwie reguły, z których pierwsza jest alternatywnym rozszerzeniem oryginalnej klasy newClassDescrule:

newClassDesc:
TC_PROXYCLASSDESCnewHandle proxyClassDescInfo

proxyClassDescInfo:
(int)<count> proxyInterfaceName classAnnotationsuperClassDesc

proxyinterfacename:
(utf)

gdy ObjectOutputStream serializuje classdescriptor dla klasy, która jest klasą proxy, określoną z pominięciem obiektuClass dla metodyProxy.isProxyClass, używa kodu typuTC_PROXYCLASSDESC zamiast TC_CLASSDESC, zgodnie z powyższymi regułami. W rozszerzeniu proxyClassDescInfo, Sekwencja pozycjiproxyinterfacename są nazwami wszystkich interfaces zaimplementowanych przez klasę proxy, w kolejności, w jakiej są zwracane przez wywołanie metody getInterfaces na obiekcie Class. Elementy classAnnotation isuperclassdesc mają takie samo znaczenie jak w regule classdescinfo. Dla klasy proxy, superclassdesci jest deskryptorem klasy dla jej superklasy,java.lang.reflect.Proxy; włączając ten deskryptor pozwala na ewolucję serializowanej reprezentacji klasy Proxy dla instancji proxy.

dla klas nie będących proxy, ObjectOutputStream wywołuje metodę itsprotected annotateClass, aby umożliwić podklasom zapisywanie niestandardowych danych do strumienia dla danej klasy. Dla proxyclasses, zamiast annotateClass, następujący method w java.io.ObjectOutputStream jest wywoływany z obiektem Class dla klasy proxy:

 protected void annotateProxyClass(Class cl) throws IOException;

Domyślna implementacja annotateProxyClass wObjectOutputStream nic nie robi.

gdy ObjectInputStream napotka kod typuTC_PROXYCLASSDESC, deserializuje classdescriptor dla klasy proxy ze strumienia, sformatowanego jak opisano powyżej. Zamiast wywoływać metodę resolveClass w celu rozwiązania obiektu Class dla classdescriptor, wywołana jest następująca metoda wjava.io.ObjectInputStream :

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

lista nazw interfejsów, które zostały deserializowane w deskryptorze proxyclass, jest przekazywana jako argument interfaces do resolveProxyClass.

Domyślna implementacja resolveProxyClass wObjectInputStream zwraca wyniki wywołaniaProxy.getProxyClass z listą obiektówClass dla interfejsów nazwanych w parametrzeinterfaces. Obiekt Class używany dla każdej nazwy interfejsu i jest wartością retunowaną przez wywołanie

 Class.forName(i, false, loader)

, gdzieloaderjest pierwszym nie-null Class loader up theexecution stos, lubnulljeśli nie null Class loaders są na stosie. Jest to ten sam wybór loadera klas dokonany przez domyślne zachowanie metodyresolveClass. Ta sama wartośćloaderjest również ładowaczem klas przekazanym doProxy.getProxyClass. JeśliProxy.getProxyClassrzuciIllegalArgumentException,resolveClassrzuciClassNotFoundExceptionzawierająceIllegalArgumentException.

ponieważ Klasa proxy nigdy nie ma własnych serializowalnych pól, dane klasy w strumieniu reprezentowanej przez instancję proxy składają się w całości z danych instancji dla jej klasy nadrzędnej,java.lang.reflect.Proxy. Proxy ma jedno pole, h, które zawiera wywołanie dla instancji proxy.

przykłady

Oto prosty przykład, który wypisuje wiadomość przed i po wywołaniu metody na obiekcie, który implementuje arbitralną listę interfejsów:

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

aby skonstruować DebugProxy dla implementacji interfejsu Foo i wywołać jedną z jego metod:

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

oto przykład klasy obsługi wywołania narzędzia, która zapewnia domyślne zachowanie serwera proxy dla metod dziedziczonych zjava.lang.Object i implementuje delegację pewnych wywołań metody proxy do różnych obiektów w zależności od interfejsu wywołanej 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()); }}

podklasy Delegator mogą nadpisaćinvokeNotDelegated, aby zaimplementować zachowanie wywołań proxymethod, które nie mogą być bezpośrednio delegowane do innych obiektów, i mogą nadpisać proxyHashCode,proxyEquals i proxyToString, aby nadpisać domyślne zachowanie metod dziedziczenia proxy z java.lang.Object.

aby skonstruować Delegator dla implementacji interfejsu Foo :

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

zauważ, że implementacja powyższej klasy Delegator ma być bardziej ilustracyjna niż zoptymalizowana; na przykład, zamiast buforować i porównywać obiekty Method dla metod hashCode, equals itoString, może po prostu dopasować je przez ich nazwy, ponieważ żadna z tych nazw metod nie jest przeciążona wjava.lang.Object.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.