15 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Динамический прокси Java: что это и как им пользоваться?

Динамический прокси / Java Dynamic Proxy

Не смотря на мой не большой опыт в java я все же могу по праву считать себя фанатиком, хлебом не корми дай поковыряться в чем нибудь и вот после очередного отвлечения на Wicket приходится вновь освежать память с чем мне обычно помогают такие люди как Юрий Ткач и Евгений Матюшкин(Skipy) своими статьями и видео за что им собственно спасибо. Так как сижу уже примерно пол года в пассивном поиске работы, фриланс спокойно позволяет заниматься любимым делом и не рваться в лапы работорговли, занимаюсь по мелочи своими стартапами. Учу что-то новое, вот в очередной раз перечитывая статью Skipy про «синхронизацию GUI« вновь встретился с такой полезной штукой как динамический прокси и поймав себя на мысли что совсем не помню что и зачем решил сделать заметку на будущее.

История

Концепция динамических прокси-классов появилась еще в 2000 году в JDK 1.3.
С тех ее используют все, кому не лень. Более того, она стала даже де-факто стандартом в некоторых областях java.

Пример

Реализуем интерфейс IUser

InvocationHandler — интерфейс, реализованный обработчиком вызова экземпляра прокси. У каждого экземпляра прокси есть связанный обработчик вызова. Когда метод вызывается на экземпляр прокси, вызов метода кодируется и диспетчеризируется invoke метод его обработчика вызова.

Далее как пользоваться… чудеса))

Ограничения и свойства

Немного теории… Создается прокси-класс с помощью вызова метода Proxy.getProxyClass, который принимает класс-лоадер и массив интерфейсов (interfaces), а возвращает объект класса java.lang.Class, который загружен с помощью переданного класс-лоадера и реализует переданный массив интерфейсов.

На передаваемые параметры есть ряд ограничений:
  1. Все объекты в массиве interfaces должны быть интерфейсами. Они не могут быть классами или примитивами.
  2. В массиве interfaces не может быть двух одинаковых объектов.
  3. Все интерфейсы в массиве interfaces должны быть загружены тем класс-лоадером, который передается в метод getProxyClass.
  4. Все не публичные интерфейсы должны быть определены в одном и том же пакете, иначе генерируемый прокси-класс не сможет их все реализовать.
  5. Ни в каких двух интерфейсах не может быть метода с одинаковым названием и сигнатурой параметров, но с разными типами возвращаемого значения.
  6. Длина массива interfaces ограничена 65535-ю интерфейсами. Никакой Java-класс не может реализовывать более 65535 интерфейсов (а так хотелось!).

Если какое-либо из вышеперечисленных ограничений нарушено — будет выброшено исключение IllegalArgumentException, а если массив интерфейсов interfaces равен null, то будет выброшено NullPointerException.

Свойства динамического прокси-класса
  1. Прокси-класс является публичным, снабжен модификатором final и не является абстрактным.
  2. Имя прокси-класса по-умолчанию не определено, однако начинается на $Proxy. Все пространство имен, начинающихся на $Proxy зарезервировано для прокси-классов.
  3. Прокси-класс наследуется от java.lang.reflect.Proxy.
  4. Прокси-класс реализует все интерфейсы, переданные при создании, в порядке передачи.
  5. Если прокси-класс реализует непубличный интерфейс, то он будет сгенерирован в том пакете, в котором определен этот самый непубличный интерфейс. В общем случае пакет, в котором будет сгенерирован прокси-класс неопределен.
  6. Метод Proxy.isProxyClass возвращает true для классов, созданных с помощью Proxy.getProxyClass и для классов объектов, созданных с помощью Proxy.newProxyInstance и false в противном случае. Данный метод используется подсистемой безопасности Java и нужно понимать, что для класса, просто унаследованного от java.lang.reflect.Proxy он вернет false.
  7. java.security.ProtectionDomain для прокси-класса такой же, как и для системных классов, загруженных bootstrap-загрузчиком, например — для java.lang.Object. Это логично, потому что код прокси-класса создается самой JVM и у нее нет причин себе не доверять.
Экземпляр динамического прокси-класса и его свойства

Конструктор прокси-класса принимает один аргумент — реализацию интерфейса InvocationHandler. Соответственно, объект прокси-класса можно создать с помощью рефлексии, вызвав метод newInstance объекта класса Class. Однако, существует и другой способ — вызвать метод Proxy.newProxyInstance, который принимает на вход загрузчик классов, массив интерфейсов, которые будет реализовывать прокси-класс, и объект, реализующий InvocationHandler. Фактически, данный метод комбинирует получение прокси-класса с помощью Proxy.getProxyClass и создание экземпляра данного класса через рефлексию.

Свойства созданного экземпляра прокси-класса следующие:

  1. Объект прокси-класса приводим ко всем интерфейсам, переданным в массиве interfaces. Если IDemo — один из переданных интерфейсов, то операция proxy instanceof IDemo всегда вернет true, а операция (IDemo) proxy завершится корректно.
  2. Статический метод Proxy.getInvocationHandler возвращает обработчик вызовов, переданный при создании экземпляра прокси-класса. Если переданный в данный метод объект не является экземпляром прокси-класса, то будет выброшено IllegalArgumentException исключение.
  3. Класс-обработчик вызовов реализует интерфейс InvocationHandler, в котором определен метод invoke, имеющий следующую сигнатуру:
Читать еще:  Использование ReSwift: написание приложения Memory Game

Здесь proxy — экземпляр прокси-класса, который может использоваться при обработке вызова того или иного метода. Второй параметр — method является экземпляром класса java.lang.reflect.Method. Значение данного параметра — один из методов, определенных в каком-либо из переданных при создании прокси-класса интерфейсов или их супер-интерфейсов. Третий параметр — массив значений аргументов метода. Аргументы примитивных типов будут заменены экземплярами своих классов-оберток, таких как java.lang.Boolean или java.lang.Integer. Конкретная реализация метода invoke может изменять данный массив.

Значение, возвращаемое методом invoke должно иметь тип, совместимый с типом значения, возвращаемого интерфейсным методом, для которого вызывается данная обертка. В частности, если интерфейсный метод возвращает значение примитивного типа — необходимо возвратить экземпляр класса-обертки данного примитивного типа. Если возвращается null, а ожидается значение примитивного типа, — будет выброшено NullPointerException. В случае непримитивных типов, класс возвращаемого значения метода invoke должен быть приводим к классу возвращаемого значения интерфейсного метода, иначе будет выброшено ClassCastException.

Внутри метода invoke должны бросаться только те проверяемые исключения, которые определены в сигнатуре вызываемого интерфейсного метода либо приводимые к ним. Помимо этих типов исключений разрешается бросать только непроверяемые исключения (такие как java.lang.RuntimeException) или ошибки (например, java.lang.Error). Если внутри метода invoke выброшено проверяемое исключение несопоставимое с описанными в сигнатуре интерфейсного метода — то будет так же выброшено исключение UndeclaredThrowableException.

Методы hashCode, equals и toString, определенные в классе Object, так же будут вызываться не на прямую, а через метод invoke наравне со всеми интерфейсными методами. Другие публичные методы класса Object будут вызываться напрямую.

Магия за кулисами

Объект User — вполне обычный, никакой магии.

Proxy.newProxyInstance — сами истоки магии, параметры вызова следующие:

  • ClassLoader класса User, о нем немного ниже;
  • Массив типаClass, должен принимать массив интерфейсов, которые реализует наш класс (User). МЕТОДЫ ЭТИХ ИНТЕРФЕЙСОВ БУДУТ ПЕРЕХВАТЫВАТЬСЯ (invocationHandler-ом).
  • Экземпляр InvocationHandler, который будет перехватыватьметоды вызываемые для объекта user (на самом деле, вызовы будут идти через вновь созданный userProxy).

На выходе получаем экземпляр некого класса(прокси), дающий следующую магическую функциональность :

Блог сурового челябинского программиста

Are you aware how much time I’ve spent learning for details of Java? Thread management, dynamics, CORBA.

воскресенье, 11 апреля 2010 г.

Об использовании динамических Proxy-классов в Java

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

Сегодня мы поговорим о такой интересной особенности JVM, как динамические прокси-классы. Предположим, что у нас есть класс A, реализующий некоторые интерфейсы. Java-машина во время исполнения может сгенерировать прокси-класс для данного класса A, т.е. такой класс, который реализует все интерфейсы класса A, но заменяет вызов всех методов этих интерфейсов на вызов метода InvocationHandler#invoke, где InvocationHandler – интерфейс JVM, для которого можно определять свои реализации.

Создается прокси-класс с помощью вызова метода Proxy.getProxyClass, который принимает класс-лоадер и массив интерфейсов (interfaces), а возвращает объект класса java.lang.Class, который загружен с помощью переданного класс-лоадера и реализует переданный массив интерфейсов.

На передаваемые параметры есть ряд ограничений:

1. Все объекты в массиве interfaces должны быть интерфейсами. Они не могут быть классами или примитивами.

2. В массиве interfaces не может быть двух одинаковых объектов.

3. Все интерфейсы в массиве interfaces должны быть загружены тем класс-лоадером, который передается в метод getProxyClass.

4. Все не публичные интерфейсы должны быть определены в одном и том же пакете, иначе генерируемый прокси-класс не сможет их все реализовать.

Читать еще:  Академия Хана (Khan Academy) - крупнейший в мире образовательный сайт

5. Ни в каких двух интерфейсах не может быть метода с одинаковым названием и сигнатурой параметров, но с разными типами возвращаемого значения.

6. Длина массива interfaces ограничена 65535-ю интерфейсами. Никакой Java-класс не может реализовывать более 65535 интерфейсов (а так хотелось!).

Если какое-либо из вышеперечисленных ограничений нарушено – будет выброшено исключение IllegalArgumentException, а если массив интерфейсов interfaces равен null, то будет выброшено NullPointerException.

Свойства динамического прокси-класса

Необходимо сказать пару слов о свойствах класса, создаваемого с помощью Proxy.getProxyClass. Данные свойства следующие:

1. Прокси-класс является публичным, снабжен модификатором final и не является абстрактным.

2. Имя прокси-класса по-умолчанию не определено, однако начинается на $Proxy. Все пространство имен, начинающихся на $Proxy зарезервировано для прокси-классов.

3. Прокси-класс наследуется от java.lang.reflect.Proxy.

4. Прокси-класс реализует все интерфейсы, переданные при создании, в порядке передачи.

5. Если прокси-класс реализует непубличный интерфейс, то он будет сгенерирован в том пакете, в котором определен этот самый непубличный интерфейс. В общем случае пакет, в котором будет сгенерирован прокси-класс неопределен.

6. Метод Proxy.isProxyClass возвращает true для классов, созданных с помощью Proxy.getProxyClass и для классов объектов, созданных с помощью Proxy.newProxyInstance и false в противном случае. Данный метод используется подсистемой безопасности Java и нужно понимать, что для класса, просто унаследованного от java.lang.reflect.Proxy он вернет false.

7. java.security.ProtectionDomain для прокси-класса такой же, как и для системных классов, загруженных bootstrap-загрузчиком, например – для java.lang.Object. Это логично, потому что код прокси-класса создается самой JVM и у нее нет причин себе не доверять.

Экземпляр динамического прокси-класса и его свойства

Конструктор прокси-класса принимает один аргумент – реализацию интерфейса InvocationHandler. Соответственно, объект прокси-класса можно создать с помощью рефлексии, вызвав метод newInstance объекта класса Class. Однако, существует и другой способ – вызвать метод Proxy.newProxyInstance, который принимает на вход загрузчик классов, массив интерфейсов, которые будет реализовывать прокси-класс, и объект, реализующий InvocationHandler. Фактически, данный метод комбинирует получение прокси-класса с помощью Proxy.getProxyClass и создание экземпляра данного класса через рефлексию.

Свойства созданного экземпляра прокси-класса следующие:

1. Объект прокси-класса приводим ко всем интерфейсам, переданным в массиве interfaces. Если IDemo – один из переданных интерфейсов, то операция proxy instanceof IDemo всегда вернет true, а операция (IDemo) proxy завершится корректно.

2. Статический метод Proxy.getInvocationHandler возвращает обработчик вызовов, переданный при создании экземпляра прокси-класса. Если переданный в данный метод объект не является экземпляром прокси-класса, то будет выброшено IllegalArgumentException исключение.

3. Класс-обработчик вызовов реализует интерфейс InvocationHandler, в котором определен метод invoke, имеющий следующую сигнатуру:

Здесь proxy – экземпляр прокси-класса, который может использоваться при обработке вызова того или иного метода. Второй параметр – method является экземпляром класса java.lang.reflect.Method. Значение данного параметра – один из методов, определенных в каком-либо из переданных при создании прокси-класса интерфейсов или их супер-интерфейсов. Третий параметр – массив значений аргументов метода. Аргументы примитивных типов будут заменены экземплярами своих классов-оберток, таких как java.lang.Boolean или java.lang.Integer. Конкретная реализация метода invoke может изменять данный массив.

Значение, возвращаемое методом invoke должно иметь тип, совместимый с типом значения, возвращаемого интерфейсным методом, для которого вызывается данная обертка. В частности, если интерфейсный метод возвращает значение примитивного типа – необходимо возвратить экземпляр класса-обертки данного примитивного типа. Если возвращается null, а ожидается значение примитивного типа, – будет выброшено NullPointerException. В случае непримитивных типов, класс возвращаемого значения метода invoke должен быть приводим к классу возвращаемого значения интерфейсного метода, иначе будет выброшено ClassCastException.

Внутри метода invoke должны бросаться только те проверяемые исключения, которые определены в сигнатуре вызываемого интерфейсного метода либо приводимые к ним. Помимо этих типов исключений разрешается бросать только непроверяемые исключения (такие как java.lang.RuntimeException) или ошибки (например, java.lang.Error). Если внутри метода invoke выброшено проверяемое исключение несопоставимое с описанными в сигнатуре интерфейсного метода – то будет так же выброшено исключение UndeclaredThrowableException.

Методы hashCode, equals и toString, определенные в классе Object, так же будут вызываться не на прямую, а через метод invoke наравне со всеми интерфейсными методами. Другие публичные методы класса Object будут вызываться напрямую.

Читать еще:  Чувствительность наушников дб какое лучше?

Пример: использование прокси-классов для обобщенного DAO

Давайте рассмотрим обещанный пример использования динамических прокси-классов. Идея взята из статьи Не повторяйте DAO!, только мы попробуем реализовать ее без использования Spring. Суть в следующем: у нас есть Hibernate, в котором есть такое понятие, как именованные запросы. Мы имеем много DAO для разных типов сущностей, в которых есть методы поиска объектов по каким-либо критериям, подсчет количества объектов и т.д. Причем, каждый метод, фактически, просто вызывает тот или иной именованный запрос и возвращает его результат. Непонятно, зачем плодить методы с одной и той же логикой. Можно просто определять методы в соответствующих интерфейсах, а вызывать их через прокси к данным интерфейсам. В прокси же вызов метода подменяется на вызов соответствующего именованного запроса (имя которого может вычисляться, например, по формуле ИМЯ_СУЩНОСТИ-ИМЯ_МЕТОДА). Правда есть одна сложность – в самом GenericDao есть методы, которые ненужно подменять, в частности это – метод load, загружающий объект из базы данных и метод save – сохраняющий объект в базе данных, соответственно.

Динамические Прокси в Java

1. Вступление

Эта статья о Java динамических прокси – это один из основных механизмов прокси, доступных нам в языке.

Проще говоря, прокси – это фронты или оболочки, которые передают вызов функции через свои собственные средства (обычно в реальные методы) – потенциально добавляя некоторую функциональность.

Динамические прокси позволяют одному единственному классу с одним единственным методом обслуживать несколько вызовов методов для произвольных классов с произвольным числом методов. Динамический прокси можно рассматривать как своего рода Facade , но тот, который может претендовать на реализацию любого интерфейса. Под прикрытием он направляет все вызовы метода одному обработчику – методу invoke () .

Хотя это не инструмент, предназначенный для повседневных задач программирования, динамические прокси могут быть весьма полезны для разработчиков фреймворков. Он также может использоваться в тех случаях, когда конкретные реализации классов не будут известны до времени выполнения.

Эта функция встроена в стандартный JDK, поэтому никаких дополнительных зависимостей не требуется.

2. Обработчик вызовов

Давайте создадим простой прокси-сервер, который на самом деле ничего не делает, кроме печати того, какой метод был запрошен для вызова, и возвращает жестко запрограммированное число.

Сначала нам нужно создать подтип java.lang.reflect.InvocationHandler :

Здесь мы определили простой прокси, который регистрирует, какой метод был вызван, и возвращает 42.

3. Создание экземпляра прокси

Экземпляр прокси, обслуживаемый только что определенным нами обработчиком вызова, создается с помощью вызова метода фабрики класса java.lang.reflect.Proxy :

Когда у нас есть экземпляр прокси, мы можем вызывать его методы интерфейса как обычно:

Как и ожидалось, сообщение о вызываемом методе put () выводится в файл журнала.

4. Обработчик вызовов через лямбда-выражения

Поскольку InvocationHandler является функциональным интерфейсом, можно определить встроенный обработчик с помощью лямбда-выражения:

Здесь мы определили обработчик, который возвращает 42 для всех операций get и выбрасывает UnsupportedOperationException для всего остального.

Он вызывается точно так же:

5. Пример динамического прокси-сервера Timing

Давайте рассмотрим один потенциальный реальный сценарий для динамических прокси

Предположим, мы хотим записать, как долго выполняются наши функции. Для этого мы сначала определяем обработчик, способный обернуть «реальный» объект, отслеживая информацию о времени и отражая вызов

Впоследствии этот прокси может использоваться на объектах различных типов:

Здесь мы проксировали карту и последовательность символов (String).

Вызовы прокси-методов делегируют обернутый объект, а также создают операторы логирования:

6. Заключение

В этом кратком руководстве мы рассмотрели динамические прокси-серверы Java, а также некоторые из его возможных применений.

Как всегда, код в примерах можно найти по адресу over на GitHub .

голоса
Рейтинг статьи
Ссылка на основную публикацию
Статьи c упоминанием слов: