Spec-Zone .ru
спецификации, руководства, описания, API
Содержание документации

Динамические Прокси-классы

Содержание

Введение
Динамический API Прокси
Сериализация
Примеры

Введение

Динамический прокси-класс является классом, который реализует список интерфейсов, определенных во время выполнения так, что, вызов метода через один из интерфейсов на экземпляре класса будет закодирован и диспетчеризирован другому объекту через универсальный интерфейс. Таким образом динамический прокси-класс может использоваться, чтобы создать безопасный с точки зрения типов объект прокси для списка интерфейсов, не требуя предварительной генерации прокси-класса, такой как с инструментами времени компиляции. Вызовы метода на экземпляре динамического прокси-класса диспетчеризируются единственному методу в обработчике вызова экземпляра, и они кодируются с a java.lang.reflect.Method объект, идентифицирующий метод, который был вызван и массив типа Object содержа параметры.

Динамические прокси-классы полезны для приложения или библиотеки, которая должна обеспечить безопасный с точки зрения типов отражающий, диспетчеризируют вызовов на объектах тот существующие интерфейсные API. Например, приложение может использовать динамический прокси-класс, чтобы создать объект, который реализует многократные интерфейсы слушателя случайного события - интерфейсы, которые расширяются java.util.EventListener- обработать множество событий различных типов универсальным способом, такой как, регистрируя все такие события к файлу.

Динамический API Прокси-класса

Динамический прокси-класс (просто называемый прокси-классом ниже) является классом, который реализует список интерфейсов, определенных во время выполнения, когда класс создается.

Интерфейс прокси является таким интерфейсом, который реализуется прокси-классом.

Экземпляр прокси является экземпляром прокси-класса.

Создание Прокси-класса

Прокси-классы, так же как экземпляры их, создаются, используя статические методы класса java.lang.reflect.Proxy.

Proxy.getProxyClass метод возвращается java.lang.Class объект для прокси-класса, данного загрузчик класса и массив интерфейсов. Прокси-класс будет определен в указанном загрузчике класса и реализует все предоставленные интерфейсы. Если прокси-класс для той же самой перестановки интерфейсов был уже определен в загрузчике класса, то существующий прокси-класс будет возвращен; иначе, прокси-класс для тех интерфейсов будет сгенерирован динамически и определен в загрузчике класса.

Есть несколько ограничений на параметры, к которым можно передать Proxy.getProxyClass:

Если какое-либо из этих ограничений нарушается, Proxy.getProxyClass бросит IllegalArgumentException. Если interfaces параметр массива или любой из его элементов null, a NullPointerException будет брошен.

Отметьте, что порядок указанных интерфейсов прокси является существенным: два запроса на прокси-класс с той же самой комбинацией интерфейсов, но в различном порядке приведут к двум отличным прокси-классам. Прокси-классы отличают по приказу их интерфейсов прокси, чтобы обеспечить детерминированное кодирование вызова метода в случаях, где два или больше из интерфейсов прокси совместно используют метод с тем же самым именем и подписью параметра; это рассуждение описывается более подробно в разделе ниже названных Методов, Дублированных в Многократных Интерфейсах Прокси.

Так, чтобы новый прокси-класс не должен был быть сгенерирован каждый раз Proxy.getProxyClass вызывается с тем же самым загрузчиком класса и списком интерфейсов, реализация динамического API прокси-класса должна сохранить кэш сгенерированных прокси-классов, включенных их соответствующими загрузчиками и интерфейсным списком. Реализация должна бояться обращаться к загрузчикам класса, интерфейсам, и прокси-классам таким способом как, чтобы предотвратить загрузчики класса, и все их классы, от того, чтобы быть собранным "мусор" когда приспособлено.

Свойства Прокси-класса

У прокси-класса есть следующие свойства:

Создание Экземпляра Прокси

У каждого прокси-класса есть один общедоступный конструктор, который берет один параметр, реализацию интерфейса InvocationHandler.

У каждого экземпляра прокси есть связанный объект-обработчик вызова, тот, который передали его конструктору. Вместо того, чтобы иметь необходимость использовать API Reflection, чтобы получить доступ к общедоступному конструктору, экземпляр прокси может быть также быть созданным, вызывая Proxy.newProxyInstance метод, который комбинирует действия вызова Proxy.getProxyClass с вызовом конструктора с обработчиком вызова. Proxy.newProxyInstance броски IllegalArgumentException по тем же самым причинам это Proxy.getProxyClass делает.

Свойства Экземпляра прокси

У экземпляра прокси есть следующие свойства:

Методы, Дублированные в Многократных Интерфейсах Прокси

Когда два или больше интерфейса прокси-класса содержат метод с тем же самым именем и подписью параметра, порядок интерфейсов прокси-класса становится существенным. Когда такой двойной метод вызывается на экземпляр прокси, 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 у экземпляра будут следующие свойства:

Потоковый протокол для Объектной Сериализации поддерживает названный код типа 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.


Oracle и/или его филиалы Авторское право © 1993, 2011, Oracle и/или его филиалы. Все права защищены.
Свяжитесь с Нами