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ówClass
. - Wszystkie typy interfejsów muszą być widoczne po nazwie przez specificated class loader. Innymi słowy, dla class loader
cl
i każdego interfejsui
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łaniegetMethods
na swoim obiekcieClass
zwróci tablicę obiektówMethod
, 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 przezProxy.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 jakjava.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ę proxyFoo
, 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.getInvocationHandler
zwróci jako argument funkcję wywołania powiązaną z instancją proxy. Jeśli obiekt przekazany doProxy.getInvocationHandler
nie jest instancją proxy,wtedy zostanie wyrzucony obiektIllegalArgumentException
. - wywołanie metody interfejsu na instancji proxy zostanie zakodowane i wysłane do metody obsługi wywołania
invoke
, jak opisano poniżej.sama instancja proxy zostanie przekazana jako pierwszy argument
invoke
, który jest typuObject
.drugim argumentem przekazanym do
invoke
będzie instancjajava.lang.reflect.Method
odpowiadająca metodzie interface wywołanej na instancji proxy. Klasa deklarująca obiektMethod
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 jakjava.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 przezinvoke
musi być instancją odpowiedniej klasy primitive wrapper; w przeciwnym razie musi być typem przypisywalnym do zadeklarowanego typu return. Jeżeli wartość zwracana przezinvoke
wynosinull
, 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.RuntimeException
lubjava.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 wthrows
klauzuli interfacemethod, wtedy wywołanie metody na instancji proxy spowoduje wywołanie wyjątkuUndeclaredThrowableException
.UndeclaredThrowableException
zostanie skonstruowany z wyjątkiem, który został wyrzucony przez metodęinvoke
. - wywołanie metod
hashCode
,equals
lubtoString
zadeklarowanych wjava.lang.Object
na instancji proxy zostanie zakodowane i wysłane do metodyinvoke
obsł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 obiektMethod
przekazany doinvoke
będzie miała wartośćjava.lang.Object
. Inne publiczne metody proxyinstance dziedziczone zjava.lang.Object
nie są obsługiwane przez klasę proxy, więc wywołania tych metod zachowują się tak samo jak dla instancjijava.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 Method
przekazany 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.Object
logicznie 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 writeExternal
i 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 iserialVersionUID
0L
. 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ócenie0L
. - wywołanie metody
getFields
zwróci zero długości tablicy. - wywołanie metody
getField
dowolnym argumentemString
zwrócinull
.
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_PROXYCLASSDESC
newHandle 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)
, gdzieloader
jest pierwszym nie-null Class loader up theexecution stos, lubnull
jeś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śćloader
jest również ładowaczem klas przekazanym doProxy.getProxyClass
. JeśliProxy.getProxyClass
rzuciIllegalArgumentException
,resolveClass
rzuciClassNotFoundException
zawierają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
.