Java dynamic proxies example

Dynamic Proxy

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

В 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, да?

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

Java-университет

Achtung. Данный материал был подан таким образjм, чтобы вы разрушили себе мозг вы сразу обратились к правильному материалу (спасибо Kurama от 14.12.2022) Перечитывать смысла нет — сразу лучше идти туда — там есть ответ на ClassCastException. От себя добавлю, что если унаследоваться от класса предка, который имплементирует, а наследник — нет, то тоже будет ClassCastException. Наследник обязан имплементировать. Прокси класс должен быть типа имплементируемого интерфейса.

только начал отходить от структур данных, поданных через одно место,теперь это. 5 раз пытался прочесть. раза 4 смотрел кино про эту дичь. вообще никак не воспринимается. на каком-то моменте просто перестаю соображать и следить че происходит из-за постоянных отсылок к предыдущим шагам. а у кого-то же воспалилась картинка с этим всем, кто-то смог придумать.. пздц

Нужно упомянуть, что при создании прокси, объект ОБЯЗАН реализовывать интерфейс, действия которого и будут перехватываться нашим прокси. Иначе не работает. Т.е. прокси можно создать только у объекта, который РЕАЛИЗУЕТ ИНТЕРФЕЙС. Если у класса объекта есть методы написанные в классе, то они перехватываться не будут.

Лучшее объяснение из всех найденных мной приводится через AOP (аспектно ориентированное программирование). В отличии от ООП инкапсуляция происходит не на уровне объектов (все информация, связанная с машиной, и методы для работы с ней должны быть инкапсулированы в соответствующий класс), а на уровне аспектов (функций, которые выполняются). Выделяется основной аспект, он же бизнес-логика (полезная работа нашего класса), и сквозные аспекты (работа, которая никак не связана с нашим классом, но должна выполняться вместе с его бизнес-логикой). К примеру нам нужно логировать каждое действие, которое происходит с нашей машиной. Логирование никакого отношения к поведению машины (бизнес-логике) не имеет, но делать его все равно нужно. Добавить этот функционал мы можем через наследование (создать класс ЛогирующаясяМашина), через декоратор (через соответствующий паттерн) или через динамические прокси, про которые нам тут рассказывают. Первые два варианта не очень подходят для больших программ, потому что нам нужно будет сделать наследника и продублировать логику логирования для каждого класса/интерфейса. Это в разы увеличит и усложнит код и вообще не выход. А вот при помощи динамеческих прокси мы можем один раз написать этот класс (вся сквозная логика будет находиться в одном месте) и во всей программе при необходимости работать с объектами именно через эти прокси, которые будут перехватывать вызовы методов основного объекта, выполнять заложенную в них сквозную логику и потом выполнять вызванный метод основного класса. В сухом остатке мы получаем, что все эти танцы с бубном нам нужны для того чтобы разделить основную и служебную логику в нашей программе и не нарушать SRP из SOLID.

Exception in thread «main» java.lang.ClassCastException: class jdk.proxy1.$Proxy0 cannot be cast to class java.io.Reader (jdk.proxy1.$Proxy0 is in module jdk.proxy1 of loader ‘app’; java.io.Reader is in module java.base of loader ‘bootstrap’) at Main.main(Main.java:21)

Exception in thread -cannot be cast to class java.io.Reader . Proxy.newProxyInstance работает только с интерфейсом, или я не прав? — Returns a proxy instance for the specified interfaces that dispatches method invocations to the specified invocation handler.

А теперь, чтобы закрепить пройденный материал, напишем приложение для выращивания картофеля на Марсе.

Для тех, кто пропустил эту ссылку пару лекций назад Ссылка на паттерн Динамические прокси (Можете и про загрузчики почитать)

 Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to . Наличие интерфейса — обязательное требование. Прокси работает на уровне интерфейсов. 

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

Источник

Динамическое генерирование прокси-классов в Java

Наверно каждому java разработчику рано или поздно потребуется использовать прокси-классы.
Под катом представлены простые примеры, выполненные при помощи JDK proxy, cglib, javassist и byte buddy.

image

Поставим себе самую простую задачу:

  • создать прокси-класс для экземпляра класса User
  • в прокси-классе необходимо перехватить метод с названием «getName»
  • результат вывода перехваченного метода должен быть в upper case
public class User implements IUser < private final String name; public User() < this(null); >public User(String name) < this.name = name; >@Override public String getName() < return name; >>

1 Стандартные средства — JDK proxy

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy;
User user = new User("Вася"); InvocationHandler handler = (proxy, method, args) -> < if(method.getName().equals("getName"))< return ((String)method.invoke(user, args)).toUpperCase(); >return method.invoke(user, args); >; IUser userProxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), User.class.getInterfaces(), handler); assertEquals("ВАСЯ", userProxy.getName());

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

2 cglib

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor;
User user = new User("Вася"); MethodInterceptor handler = (obj, method , args, proxy) -> < if(method.getName().equals("getName"))< return ((String)proxy.invoke(user, args)).toUpperCase() ; >return proxy.invoke(user, args); >; User userProxy = (User) Enhancer.create(User.class, handler); assertEquals("ВАСЯ", userProxy.getName());

3 Javassist

import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import javassist.util.proxy.ProxyObject;
User user = new User("Вася"); MethodHandler handler = (self, overridden, forwarder, args) -> < if(overridden.getName().equals("getName"))< return ((String)overridden.invoke(user, args)).toUpperCase(); >return overridden.invoke(user, args); >; ProxyFactory factory = new ProxyFactory(); factory.setSuperclass(User.class); Object instance = factory.createClass().newInstance(); ((ProxyObject) instance).setHandler(handler); User userProxy = (User) instance; assertEquals("ВАСЯ", userProxy.getName());

4 Byte Buddy

import net.bytebuddy.ByteBuddy; import net.bytebuddy.implementation.MethodDelegation; import static net.bytebuddy.matcher.ElementMatchers.named;
User user = new User("Вася"); User userProxy = new ByteBuddy() .subclass(User.class) .method(named("getName")) .intercept(MethodDelegation.to(new MyInterceptor(user))) .make() .load(User.class.getClassLoader()) .getLoaded() .newInstance(); assertEquals("ВАСЯ", userProxy.getName());
public class MyInterceptor < User user; public MyInterceptor(User user) < this.user = user; >public String getName() < return user.getName().toUpperCase(); >>

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

Источник

Читайте также:  Facts about python programming
Оцените статью