|
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.