Что такое proxy java

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

Ну что ж до Нового года и старта десятого потока «Разработчик Java» осталось совсем шуть-шуть. Так что у нас остался один открытый урок, который мы подготавливаем для публикации и сегодняшняя заметка, из которой вы узнаете о динамическом прокси Java: что это такое, когда и как его использовать в коде.

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

Таким образом, прокси-классы удобно реализуют многие вещи:

Все это происходит без изменений оригинального кода класса. Полный список не ограничивается примерами выше, они лишь его малая часть.

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

Хоть шаблон прокси применяется не только для создания прокси-объекта и класса в среде выполнения, в Java это особенно интересная тема. В этой статье я фокусируюсь именно на таких прокси.

Это сложная тема, которая требует использования класса отражения, или манипулирования байт-кодом, или компиляции Java-кода, сгенерированного динамически. А может всего и сразу. Чтобы новый класс не был доступен в качестве байт-кода во время исполнения, потребуются сгенерированный байт-код и загрузчик классов для загрузки байт-кода. Для создания байт-кода, используйте cglib, bytebuddy или встроенный компилятор Java.

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

Как этим пользоваться в нашем коде?

Для вызова оригинального метода исходного объекта, обработчику необходим доступ к нему. Что не предоставлено реализацией прокси Java. Вам понадобится самостоятельно передать аргумент инстансу обработчика в коде. (Обратите внимание на объект (обычно с названием proxy), который передается в качестве аргумента вызываемому обработчику. Это прокси-объект, который отражение Java генерирует динамически, а не тот объект, что мы хотим проксировать.) Таким образом, вы можете использовать как отдельные объекты-обработчики для каждого исходного класса, так и общий объект, который знает, как вызвать оригинальный объект, если для этого вообще есть какой-либо метод.

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

Если же проксируемый класс не реализует интерфейс, стоит задуматься об использовании какой-либо иной реализации прокси.

Ждём ваши комментарии и вопросы. Как всегда или тут, или можно зайти к Виталию на день открытых дверей.

Источник

Паттерн проектирования Proxy

Для чего нужен Заместитель

Пример 1

Пример 2

Принцип работы паттерна

Для каких задач лучше использовать Proxy

Преимущества и недостатки

Паттерн Заместитель на практике

Создать класс заместителя. В нем должна быть ссылка на сервисный объект (создать в классе или передать в конструкторе);

Вот наш класс-заместитель:

На этом этапе просто создаем класс со ссылкой на оригинальный объект и передаем все вызовы ему.

Реализовываем логику класса-заместителя. В основном вызов всегда перенаправляется оригинальному объекту.

Метод getTimetable() проверяет, закэширован ли массив расписания в память. Если нет, он посылает запрос для загрузки данных с диска, сохраняя результат. Если же запрос уже выполняется, он быстро вернет объект из памяти.

Благодаря простому функционалу, метод getTrainDepartireTime() не пришлось перенаправлять в оригинальный объект. Мы просто дублировали его функционал в новый метод.

Так делать нельзя. Если пришлось дублировать код или производить подобные манипуляции, значит что-то пошло не так, и нужно посмотреть на проблему под другим углом. В нашем простом примере иного пути нет, но в реальных проектах, скорее всего, код будет написан более корректно.

Заменить в клиентском коде создание оригинального объекта на объект-заместитель:

Отлично, работает корректно.

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

Что такое proxy java. Смотреть фото Что такое proxy java. Смотреть картинку Что такое proxy java. Картинка про Что такое proxy java. Фото Что такое proxy java

Пара полезных ссылок вместо точки

Источник

Dynamic Proxy

— Сегодня я расскажу тебе новую и очень интересную тему — динамические прокси.

В Java есть несколько способов изменить функциональность нужного класса…

Способ первый. Наследование

Самый простой способ изменить поведение некоторого класса — это создать новый класс, унаследовать его от оригинального (базового) и переопределить его методы. Затем, вместо объектов оригинального класса использовать объекты класса наследника. Пример:

Способ второй. Использование класса-обертки (Wrapper).

Способ третий. Создание динамического прокси (Proxy).

Что такое proxy java. Смотреть фото Что такое proxy java. Смотреть картинку Что такое proxy java. Картинка про Что такое proxy java. Фото Что такое proxy java

В Java есть специальный класс (java.lang.reflect.Proxy), с помощью которого фактически можно сконструировать объект во время исполнения программы (динамически), не создавая для него отдельного класса.

Это делается очень просто:

— А вот это уже что-то новенькое!

Invoke – стандартное название для метода/класса, основная задача которого просто вызвать какой-то метод.

Handler – стандартное название для класса, который обрабатывает какое-то событие. Например, обработчик клика мышки будет называться MouseClickHandler, и т.д.

— Т.е. мы объявили класс CustomInvocationHandler, в нем реализовали интерфейс InvocationHandler и его метод invoke. Метод invoke при вызове выводит на экран строку “yes!”- Затем мы создали объект типа CustomInvocationHandler и передали его в метод newProxyInstance при создании объекта-proxy.

Это очень мощный инструмент, обычно создание таких прокси используется для имитации объектов из программ, которые физически запущены на другом компьютере. Или для контроля доступа

– в таком методе можно проверять права текущего пользователя, обрабатывать ошибки, логировать ошибки и многое другое.

Вот пример, где метод invoke еще и вызывает методы оригинального объекта:

В данном примере есть две особенности.

Во-вторых, в методе invoke мы снова вызываем этот же метод, но уже у «оригинального» объекта.

— Ага. Т.е. вот эта последняя строчка и есть вызов того же самого метода, но уже у оригинального объекта:

— Не сказал бы, что слишком очевидно, но все же понятно. Вроде бы.

— Отлично. Тогда вот еще что. В метод newProxyInstance нужно передавать еще немного служебной информации для создания proxy-объекта. Но, т.к. мы не создаем монструозные прокси-объекты, то эту информацию легко получить из самого оригинального класса.

— Ага. ClassLoader и список интерфейсов. Это что-то из Reflection, да?

— Ясно. Что ж, думаю, я смогу создать примитивный простенький прокси объект, если это когда-нибудь мне понадобится.

Источник

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

Что такое proxy java. Смотреть фото Что такое proxy java. Смотреть картинку Что такое proxy java. Картинка про Что такое proxy java. Фото Что такое proxy java

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

История

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

Пример

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

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

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

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

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

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

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

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

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

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

Здесь 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 — сами истоки магии, параметры вызова следующие:

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

Источник

Паттерн проектирования «Заместитель» / «Proxy»

Почитать описание других паттернов.

Проблема

Необходимо контролировать доступ к объекту, не изменяя при этом поведение клиента.

Описание

При проектировании сложных систем, достаточно часто возникает необходимость обеспечить контролируемый доступ к определенным объектам системы. Мотивацией для этого служит ряд приобретаемых преимуществ. Таких как, ленивая инициализация по требованию для «громоздких» объектов, подсчет количества ссылок на объект и т.д. и т.п. Однако, не всегда потребность в контролируемом доступе к объекту базируется только на преимуществах. Как правило, сложность процессов реального мира, ограничения вычислительных ресурсов просто не оставляют проектировщику выбора, нежели как воспользоваться паттерном «Заместитель» («Сурогат»).

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

Такой подход позволяет неявным для клиента образом контролировать доступ к объекту.

Практическая задача

Рассмотрим задачу реализации игры «Сапер». Будем полагать, что для ячеек (Cell), которые бывают заминированными (Mine) и пустыми (Empty), существуют некоторые громоздкие графические изображения. Для заминированной ячейки — мина, для пустой ячейки изображение с количеством мин в соседних клетках. При этом, само изображение хранится в каждой ячейке и инстанциируется в момент ее создания. Игрок же, видит изображение ячейки только после ее открытия (операция open()). Поэтому, было бы разумным инстанциировать ячейки в тот момент, когда игрок пытается их открыть, чтобы сократить расходы общей памяти для хранения изображений. Однако, такой подход тут применить нельзя. Дело в том, что до операции open() у каждой ячейки вызываются операции getTop(), getLeft() для получения координат ячейки. Но если ячейка еще не будет создана, о каких ее координатах может идти речь?

Использование паттерна прокси решает данную проблему. Вместо оригинальных объектов клиент будет использовать их земестители (MineProxy, EmptyProxy). При этом становится возможной ленивая инициализация ячеек, ввиду того, что оригинальный объект создается лишь при вызове операции open() у прокси а на запросы о получении координат (getTop(), getLeft()) прокси отвечает самостоятельно, по крайней мере до момента создания оригинального объекта.

Диаграмма классов

Что такое proxy java. Смотреть фото Что такое proxy java. Смотреть картинку Что такое proxy java. Картинка про Что такое proxy java. Фото Что такое proxy java

Реализация на Java

/**
* Абстрактный класс ячейки минного поля
*/
public abstract class Cell <
public static final int OPENED = 0;
public static final int CLOSED = 1;

protected int status;

protected int left, top;

public Cell( int left, int top) <
super();

public int getLeft() <
return left;
>

public int getTop() <
return top;
>

public int getStatus() <
return status;
>

/**
* Единственная абстрактная операция, возвращаяет количество очков за открытие данной ячейки.
*/
public abstract int getPoints();
>

/**
* Уточнение ячейки минного поля, в качестве пустой ячейки
*/
public class Empty extends Cell <

public Empty( int left, int top) <
super(left, top);

// загружаем тяжелое изображение пустой ячейки.
>

@Override
public int getPoints() <
return 10; // 10 очков за открытую пустую ячейку
>
>

/**
* Уточнение ячейки, как ячейки с миной.
*/
public class Mine extends Cell <

public Mine( int left, int top) <
super(left, top);

// загружаем тяжелое изображение ячейки c миной
>

@Override
public int getPoints() <
return 100; // 100 очков за открытую мину
>
>

/**
* Прокси для пустой ячейки
*/
public class EmptyProxy extends Cell <
private Empty proxy; // ссылка на пустую ячейку

/**
* Ленивая инициализация пустой ячейки
*/
@Override
public void open() <
if (proxy == null ) <
proxy = new Empty(left, top);
>

@Override
public int getLeft() <
if (proxy == null ) <
return left;
> else <
return proxy.getLeft();
>

@Override
public int getTop() <
if (proxy == null ) <
return top;
> else <
return proxy.getTop();
>
>

@Override
public int getStatus() <
if (proxy == null ) <
return status;
> else <
return proxy.getStatus();
>
>

@Override
public int getPoints() <
if (proxy == null ) <
return 10;
> else <
return proxy.getPoints();
>
>
>

/**
* Прокси для ячейки с миной
*/
public class MineProxy extends Cell <
private Mine proxy;

public MineProxy( int left, int top) <
super(left, top);

/**
* Ленивая инициализация ячейки с миной
*/
@Override
public void open() <
if (proxy == null ) <
proxy = new Mine(left, top);
>

@Override
public int getLeft() <
if (proxy == null ) <
return left;
> else <
return proxy.getLeft();
>

@Override
public int getTop() <
if (proxy == null ) <
return top;
> else <
return proxy.getTop();
>
>

@Override
public int getStatus() <
if (proxy == null ) <
return status;
> else <
return proxy.getStatus();
>
>

@Override
public int getPoints() <
if (proxy == null ) <
return 100;
> else <
return proxy.getPoints();
>
>
>

/**
* Использование
*/
public class Main <
public static void main( String [] args) <
Cell cells[][] = new Cell[10][10];

for ( int i=0; i for ( int j=0; j if (i+j % 2 == 0) <
cells[i][j] = new MineProxy(i, j);
> else <
cells[i][j] = new EmptyProxy(i, j);
>
>
>

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *