Spec-Zone .ru
спецификации, руководства, описания, API
|
Динамический прокси-класс является классом, который реализует список интерфейсов, определенных во время выполнения так, что, вызов метода через один из интерфейсов на экземпляре класса будет закодирован и диспетчеризирован другому объекту через универсальный интерфейс. Таким образом динамический прокси-класс может использоваться, чтобы создать безопасный с точки зрения типов объект прокси для списка интерфейсов, не требуя предварительной генерации прокси-класса, такой как с инструментами времени компиляции. Вызовы метода на экземпляре динамического прокси-класса диспетчеризируются единственному методу в обработчике вызова экземпляра, и они кодируются с a java.lang.reflect.Method
объект, идентифицирующий метод, который был вызван и массив типа Object
содержа параметры.
Динамические прокси-классы полезны для приложения или библиотеки, которая должна обеспечить безопасный с точки зрения типов отражающий, диспетчеризируют вызовов на объектах тот существующие интерфейсные API. Например, приложение может использовать динамический прокси-класс, чтобы создать объект, который реализует многократные интерфейсы слушателя случайного события - интерфейсы, которые расширяются java.util.EventListener
- обработать множество событий различных типов универсальным способом, такой как, регистрируя все такие события к файлу.
Динамический прокси-класс (просто называемый прокси-классом ниже) является классом, который реализует список интерфейсов, определенных во время выполнения, когда класс создается.
Интерфейс прокси является таким интерфейсом, который реализуется прокси-классом.
Экземпляр прокси является экземпляром прокси-класса.
Прокси-классы, так же как экземпляры их, создаются, используя статические методы класса java.lang.reflect.Proxy.
Proxy.getProxyClass
метод возвращается java.lang.Class
объект для прокси-класса, данного загрузчик класса и массив интерфейсов. Прокси-класс будет определен в указанном загрузчике класса и реализует все предоставленные интерфейсы. Если прокси-класс для той же самой перестановки интерфейсов был уже определен в загрузчике класса, то существующий прокси-класс будет возвращен; иначе, прокси-класс для тех интерфейсов будет сгенерирован динамически и определен в загрузчике класса.
Есть несколько ограничений на параметры, к которым можно передать Proxy.getProxyClass
:
Class
объекты в interfaces
массив должен представить интерфейсы, не классы или типы примитивов.interfaces
массив может обратиться к идентичному Class
объекты.cl
и каждый интерфейс i
, следующее выражение должно быть истиной: Class.forName(i.getName(), false, cl) == i
interfaces
массив не должен превысить 65535.Если какое-либо из этих ограничений нарушается, Proxy.getProxyClass
бросит IllegalArgumentException
. Если interfaces
параметр массива или любой из его элементов null
, a NullPointerException
будет брошен.
Отметьте, что порядок указанных интерфейсов прокси является существенным: два запроса на прокси-класс с той же самой комбинацией интерфейсов, но в различном порядке приведут к двум отличным прокси-классам. Прокси-классы отличают по приказу их интерфейсов прокси, чтобы обеспечить детерминированное кодирование вызова метода в случаях, где два или больше из интерфейсов прокси совместно используют метод с тем же самым именем и подписью параметра; это рассуждение описывается более подробно в разделе ниже названных Методов, Дублированных в Многократных Интерфейсах Прокси.
Так, чтобы новый прокси-класс не должен был быть сгенерирован каждый раз Proxy.getProxyClass
вызывается с тем же самым загрузчиком класса и списком интерфейсов, реализация динамического API прокси-класса должна сохранить кэш сгенерированных прокси-классов, включенных их соответствующими загрузчиками и интерфейсным списком. Реализация должна бояться обращаться к загрузчикам класса, интерфейсам, и прокси-классам таким способом как, чтобы предотвратить загрузчики класса, и все их классы, от того, чтобы быть собранным "мусор" когда приспособлено.
У прокси-класса есть следующие свойства:
"$Proxy"
должен, однако, быть зарезервирован для прокси-классов.java.lang.reflect.Proxy
.getInterfaces
на Class
объект возвратит массив, содержащий тот же самый список интерфейсов (в порядке, определенном при его создании), вызывая getMethods
на Class
объект возвратит массив Method
объекты, которые включают все методы в тех интерфейсах, и вызов getMethod
найдет методы в интерфейсах прокси, как ожидался бы.Proxy.isProxyClass
метод возвратит true, если это передадут прокси-класс - класс, возвращенный Proxy.getProxyClass
или класс объекта, возвращенного Proxy.newProxyInstance
- и ложь иначе. Надежность этого метода важна для возможности использовать это, чтобы принять решения безопасности, таким образом, ее реализация не должна просто протестировать, если рассматриваемый класс расширяется java.lang.reflect.Proxy
.java.security.ProtectionDomain
из прокси-класса то же самое как тот из системных классов, загруженных загрузчиком класса начальной загрузки, такой как java.lang.Object
, потому что код для прокси-класса сгенерирован кодом достоверной системы. Этот домен защиты будут обычно предоставлять java.security.AllPermission
.У каждого прокси-класса есть один общедоступный конструктор, который берет один параметр, реализацию интерфейса InvocationHandler
.
У каждого экземпляра прокси есть связанный объект-обработчик вызова, тот, который передали его конструктору. Вместо того, чтобы иметь необходимость использовать API Reflection, чтобы получить доступ к общедоступному конструктору, экземпляр прокси может быть также быть созданным, вызывая Proxy.newProxyInstance
метод, который комбинирует действия вызова Proxy.getProxyClass
с вызовом конструктора с обработчиком вызова. Proxy.newProxyInstance
броски IllegalArgumentException
по тем же самым причинам это Proxy.getProxyClass
делает.
У экземпляра прокси есть следующие свойства:
proxy
и один из интерфейсов реализуется его прокси-классом Foo
, следующее выражение возвратит true:
proxy instanceof Foo
и следующая работа броска успешно выполнится (вместо того, чтобы бросить a ClassCastException
):
(Foo) proxy
Proxy.getInvocationHandler
метод возвратит обработчик вызова, связанный с экземпляром прокси, который передают как его параметр. Если объект, к которому передают Proxy.getInvocationHandler
не экземпляр прокси, затем IllegalArgumentException
будет брошен.invoke
метод как описано ниже. Экземпляр самого прокси передадут как первый параметр invoke
, который имеет тип Object
.
Второй параметр, к которому передают invoke
будет java.lang.reflect.Method
экземпляр, соответствующий интерфейсному методу, вызывается на экземпляр прокси. Класс объявления Method
объект будет интерфейсом, в котором был объявлен метод, который может быть суперинтерфейсом интерфейса прокси, что прокси-класс наследовал метод через.
Третий параметр, к которому передают invoke
будет массив объектов, содержащих значения параметров, которые передают в вызове метода экземпляру прокси. Параметры типов примитивов обертываются в экземпляр соответствующего примитивного класса обертки, такой как java.lang.Integer
или java.lang.Boolean
. Реализация invoke
метод свободен изменить содержание этого массива.
Значение, возвращенное invoke
метод станет возвращаемым значением вызова метода на экземпляре прокси. Если объявленное возвращаемое значение интерфейсного метода является типом примитива, то значение, возвращенное invoke
должен быть экземпляр соответствующего примитивного класса обертки; иначе, это должен быть тип, присваиваемый объявленному типу возврата. Если значение, возвращенное invoke
null
и тип возврата интерфейсного метода примитивен, тогда a NullPointerException
будет брошен вызовом метода на экземпляре прокси. Если значение, возвращенное invoke
является иначе не совместимым с объявленным типом возврата метода как описано выше, a ClassCastException
будет брошен экземпляром прокси.
Если исключение выдается invoke
метод, это будет также брошено вызовом метода на экземпляре прокси. Тип исключения должен быть присваиваемым или любому из типов исключения, объявленных в подписи интерфейсного метода или к типам исключения непроверенным java.lang.RuntimeException
или java.lang.Error
. Если проверенное исключение выдается invoke
это не присваиваемо любому из типов исключения, объявленных в throws
пункт интерфейсного метода, затем UndeclaredThrowableException
будет брошен вызовом метода на экземпляре прокси. UndeclaredThrowableException
будет создан за исключением того, что был брошен invoke
метод.
hashCode
, equals
, или toString
методы, объявленные в java.lang.Object
на прокси экземпляр будет закодирован и диспетчеризирован обработчику вызова invoke
метод тем же самым способом как интерфейсные вызовы метода кодируется и диспетчеризируется, как описано выше. Класс объявления Method
объект, к которому передают invoke
будет java.lang.Object
. Другие открытые методы экземпляра прокси, наследованного от java.lang.Object
не переопределяются прокси-классом, таким образом, вызовы тех методов ведут себя как, они делают для экземпляров java.lang.Object
.Когда два или больше интерфейса прокси-класса содержат метод с тем же самым именем и подписью параметра, порядок интерфейсов прокси-класса становится существенным. Когда такой двойной метод вызывается на экземпляр прокси, Method
объект, который передают к обработчику вызова, не обязательно будет тем, объявление которого класса присваиваемо от ссылочного типа интерфейса, что метод прокси был вызван через. Это ограничение существует, потому что соответствующая реализация метода в сгенерированном прокси-классе не может определить, какой интерфейс это было вызвано через. Поэтому, когда двойной метод вызывается на экземпляр прокси, Method
объект для метода в передовом интерфейсе, который содержит метод (или непосредственно или наследованный через суперинтерфейс) в списке прокси-класса интерфейсов, передают к обработчику вызова invoke
метод, независимо от ссылочного типа, через который произошел вызов метода.
Если интерфейс прокси содержит метод с тем же самым именем и подписью параметра как hashCode
, equals
, или toString
методы java.lang.Object
, когда такой метод вызывается на экземпляр прокси, Method
объект, который передают к обработчику вызова, будет иметь java.lang.Object
как его объявление класса. Другими словами, общественность, незаключительные методы java.lang.Object
логически предшествуйте всем интерфейсам прокси для определения который Method
возразите, чтобы передать к обработчику вызова.
Отметьте также это, когда двойной метод диспетчеризируется обработчику вызова, invoke
метод может только бросить проверенные типы исключения, которые присваиваемы одному из исключения, вводит throws
пункт метода во всех интерфейсах прокси, что это может быть вызвано через. Если invoke
метод выдает проверенное исключение, которое не присваиваемо любому из типов исключения, объявленных методом в одном из интерфейсов прокси, что это может быть вызвано через, затем непроверенное UndeclaredThrowableException
будет брошен вызовом на экземпляре прокси. Это ограничение означает что не все типы исключения, возвращенные, вызывая getExceptionTypes
на Method
объект, который передают к invoke
метод может обязательно быть брошен успешно invoke
метод.
С тех пор java.lang.reflect.Proxy
реализации java.io.Serializable
, экземпляры прокси могут быть сериализированы, как описано в этом разделе. Если экземпляр прокси содержит обработчик вызова, который не присваиваем java.io.Serializable
, однако, тогда a java.io.NotSerializableException
будет брошен, если такой экземпляр будет записан a java.io.ObjectOutputStream
. Отметьте это прокси-классами, реализовывая java.io.Externalizable
имеет тот же самый эффект относительно сериализации как реализация java.io.Serializable
: writeExternal
и readExternal
методы Externalizable
интерфейс никогда не будет вызываться на экземпляр прокси (или обработчик вызова) как часть его процесса сериализации. Как со всеми Class
объекты, Class
объект для прокси-класса всегда сериализуем.
У прокси-класса нет никаких сериализуемых полей и a serialVersionUID
из 0L
. Другими словами, когда Class
объект для прокси-класса передают к помехам lookup
метод java.io.ObjectStreamClass
, возвращенный ObjectStreamClass
у экземпляра будут следующие свойства:
getSerialVersionUID
метод возвратится 0L
.getFields
метод возвратит массив нуля длины.getField
метод с любым String
параметр возвратится null
.Потоковый протокол для Объектной Сериализации поддерживает названный код типа TC_PROXYCLASSDESC
, который является терминальным символом в грамматике для потокового формата; его тип и значение определяются следующим постоянным полем в java.io.ObjectStreamConstants
интерфейс:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
Грамматика также включает следующие два правила, первое, являющееся альтернативным расширением исходного правила newClassDesc:
newClassDesc:
TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:
(int)<count>
proxyInterfaceName [количество] classAnnotation
superClassDesc
proxyInterfaceName:
(utf)
Когда ObjectOutputStream
сериализирует дескриптор класса для класса, который является прокси-классом, как определено, передавая Class
возразите против Proxy.isProxyClass
метод, это использует TC_PROXYCLASSDESC
введите код вместо TC_CLASSDESC
, после правил выше. В расширении proxyClassDescInfo последовательность proxyInterfaceName элементов является именами всех интерфейсов, реализованных прокси-классом в порядке, что они возвращаются, вызывая getInterfaces
метод на Class
объект. У classAnnotation и superClassDesc элементов есть то же самое значение, как они делают в правиле classDescInfo. Для прокси-класса superClassDesc является дескриптором класса для своего суперкласса, java.lang.reflect.Proxy
; включая этот дескриптор учитывает развитие сериализированного представления класса Proxy
для экземпляров прокси.
Для непрокси-классов, ObjectOutputStream
вызывает его защищенный annotateClass
метод, чтобы позволить подклассам писать пользовательские данные в поток для определенного класса. Для прокси-классов, вместо annotateClass
, следующий метод в java.io.ObjectOutputStream
вызывается с Class
объект для прокси-класса:
protected void annotateProxyClass(Class cl) throws IOException;
Реализация по умолчанию annotateProxyClass
в ObjectOutputStream
ничего не делает.
Когда ObjectInputStream
встречается с кодом типа TC_PROXYCLASSDESC
, это десериализовывает дескриптор класса для прокси-класса от потока, отформатированного как описано выше. Вместо того, чтобы вызвать resolveClass
метод, чтобы решить Class
объект для дескриптора класса, следующего метода в java.io.ObjectInputStream
вызывается:
protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException;
Список интерфейсных имен, которые были десериализованы в дескрипторе прокси-класса, передают как interfaces
параметр resolveProxyClass
.
Реализация по умолчанию resolveProxyClass
в ObjectInputStream
возвращает результаты вызова Proxy.getProxyClass
со списком Class
объекты для интерфейсов, названных в interfaces
параметр. Class
объект используется для каждого интерфейсного имени i
значение, повторно настроенное, вызывая
Class.forName(i, false, loader)где
loader
первый ненулевой загрузчик класса стек выполнения, или null
если никакие ненулевые загрузчики класса не находятся на стеке. Это - тот же самый выбор загрузчика класса, сделанный поведением по умолчанию resolveClass
метод. Это то же самое значение loader
также загрузчик класса, к которому передают Proxy.getProxyClass
. Если Proxy.getProxyClass
броски IllegalArgumentException
, resolveClass
бросит a ClassNotFoundException
содержа IllegalArgumentException
. Так как у прокси-класса никогда нет своих собственных сериализуемых полей, classdata [] в потоковом представлении экземпляра прокси состоит полностью из данных экземпляра для его суперкласса, java.lang.reflect.Proxy
. Proxy
имеет одно сериализуемое поле, h
, который содержит обработчик вызова для экземпляра прокси.
Вот простой пример, который распечатывает сообщение прежде и после вызова метода на объекте, который реализует произвольный список интерфейсов:
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; } }
Создать a DebugProxy
для реализации Foo
соедините интерфейсом и вызовите один из его методов:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
Вот пример служебного класса обработчика вызова, который обеспечивает поведение прокси по умолчанию для методов, наследованных от java.lang.Object
и делегация реализаций определенных вызовов метода прокси к отличным объектам в зависимости от интерфейса вызванного метода:
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[0]); } 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[i])) { try { return m.invoke(delegates[i], 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()); } }
Подклассы Delegator
может переопределить invokeNotDelegated
чтобы реализовать поведение вызовов метода прокси, которые не будут непосредственно делегированы к другим объектам, и они могут переопределить proxyHashCode
, proxyEquals
, и proxyToString
чтобы переопределить поведение по умолчанию методов, прокси наследовался от java.lang.Object
.
Создать a Delegator
для реализации Foo
интерфейс:
Class[] proxyInterfaces = new Class[] { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object[] { new FooImpl() }));
Отметьте что реализация Delegator
класс, данный выше, предназначается, чтобы быть более иллюстративным чем оптимизированный; например, вместо того, чтобы кэшироваться и сравниться Method
объекты для hashCode
, equals
, и toString
методы, это могло только соответствовать им их названиями строк, потому что ни одни из тех имен методов не перегружаются в java.lang.Object
.