— Привет, Амиго.
— Здорово, Риша.
— Сегодня я расскажу тебе новую и очень интересную тему — динамические прокси.
В Java есть несколько способов изменить функциональность нужного класса…
Способ первый. Наследование
Самый простой способ изменить поведение некоторого класса — это создать новый класс, унаследовать его от оригинального (базового) и переопределить его методы. Затем, вместо объектов оригинального класса использовать объекты класса наследника. Пример:
Reader reader = new UserCustomReader();
Способ второй. Использование класса-обертки (Wrapper).
Примером такого класса является BufferedReader. Во-первых, он унаследован от Reader, то есть может быть использован вместо него. Во-вторых, он переадресует все вызовы к оригинальному объекту Reader, который обязательно нужно передать в конструкторе объекту BufferedReader. Пример:
Reader readerOriginal = new UserCustomReader();
Reader reader = new BufferedReader(readerOriginal);
Способ третий. Создание динамического прокси (Proxy).

В Java есть специальный класс (java.lang.reflect.Proxy), с помощью которого фактически можно сконструировать объект во время исполнения программы (динамически), не создавая для него отдельного класса.
Это делается очень просто:
Reader reader = (Reader)Proxy.newProxyInstance();
— А вот это уже что-то новенькое!
— Но, нам ведь не нужен просто объект без методов. Надо чтобы у этого объекта были методы, и они делали то, что нам нужно. Для этого в Java используется специальный интерфейс InvocationHandler, с помощью которого можно перехватывать все вызовы методов, обращенные к proxy-объекту. proxy-объект можно создать только используя интерфейсы.
Invoke – стандартное название для метода/класса, основная задача которого просто вызвать какой-то метод.
Handler – стандартное название для класса, который обрабатывает какое-то событие. Например, обработчик клика мышки будет называться MouseClickHandler, и т.д.
У интерфейса InvocationHandler есть единственный метод invoke, в который направляются все вызовы, обращенные к proxy-объекту. Пример:
Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler());
reader.close();
class CustomInvocationHandler implements InvocationHandler
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
System.out.println("yes!");
return null;
}
}
При вызове метода reader.close(), вызовется метод invoke, и на экран будет выведена надпись “yes!”
— Т.е. мы объявили класс CustomInvocationHandler, в нем реализовали интерфейс InvocationHandler и его метод invoke. Метод invoke при вызове выводит на экран строку “yes!”- Затем мы создали объект типа CustomInvocationHandler и передали его в метод newProxyInstance при создании объекта-proxy.
— Да, все верно.
Это очень мощный инструмент, обычно создание таких прокси используется для имитации объектов из программ, которые физически запущены на другом компьютере. Или для контроля доступа
– в таком методе можно проверять права текущего пользователя, обрабатывать ошибки, логировать ошибки и многое другое.
Вот пример, где метод invoke еще и вызывает методы оригинального объекта:
Reader original = new UserCustomReader(); Reader reader = (Reader)Proxy.newProxyInstance(new CustomInvocationHandler(original));
reader.close();
class CustomInvocationHandler implements InvocationHandler { private Reader readerOriginal; CustomInvocationHandler(Reader readerOriginal)
{
this.readerOriginal = readerOriginal; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.getName().equals("close"))
{
System.out.println("Reader closed!");
}
// это вызов метода close у объекта readerOriginal
// имя метода и описание его параметров хранится в переменной method
return method.invoke(readerOriginal, args);
}
}
В данном примере есть две особенности.
Во-первых, в конструктор передается «оригинальный» объект Reader, ссылка на который сохраняется внутри CustomInvocationHandler.
Во-вторых, в методе invoke мы снова вызываем этот же метод, но уже у «оригинального» объекта.
— Ага. Т.е. вот эта последняя строчка и есть вызов того же самого метода, но уже у оригинального объекта:
return method.invoke(readerOriginal, args);
— Ага.
— Не сказал бы, что слишком очевидно, но все же понятно. Вроде бы.
— Отлично. Тогда вот еще что. В метод newProxyInstance нужно передавать еще немного служебной информации для создания proxy-объекта. Но, т.к. мы не создаем монструозные прокси-объекты, то эту информацию легко получить из самого оригинального класса.
Вот тебе пример:
Reader original = new UserCustomReader();
ClassLoader classLoader = original.getClass().getClassLoader();
Class<?>[] interfaces = original.getClass().getInterfaces();
CustomInvocationHandler invocationHandler = new CustomInvocationHandler(original);
Reader reader = (Reader)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
class CustomInvocationHandler implements InvocationHandler
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
return null;
}
}
— Ага. ClassLoader и список интерфейсов. Это что-то из Reflection, да?
— Ага.
— Ясно. Что ж, думаю, я смогу создать примитивный простенький прокси объект, если это когда-нибудь мне понадобится.
— А ты спроси у Диего
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ