Dynamic proxy java 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-университет

Может кому будет полезно изложение этой темы без кода: «Dynamic Proxy — это механизм в языке программирования Java, позволяющий создавать объекты, которые могут перехватывать вызовы методов и добавлять дополнительное поведение перед или после выполнения этих методов. Допустим, у вас есть класс или интерфейс, и вы хотите добавить некоторую дополнительную функциональность к его методам без изменения его исходного кода. Например, вы можете захотеть добавить логирование, мониторинг или проверку безопасности. Dynamic Proxy позволяет вам создать объект, который выглядит и ведет себя так же, как и оригинальный класс или интерфейс, но с дополнительными возможностями. Вы можете перехватывать вызовы методов на этом прокси-объекте и добавлять свой код до или после выполнения оригинального метода. Например, представьте, что у вас есть класс для отправки электронных писем, и вы хотите добавить логирование каждого отправленного письма. Вместо того, чтобы изменять код этого класса и вносить в него логирование, вы можете использовать Dynamic Proxy. Вы создаете прокси-объект для класса отправки писем, который реализует тот же интерфейс. Когда вызывается метод отправки письма на прокси-объекте, он перехватывается прокси-механизмом, и вы можете добавить код для записи лога перед вызовом оригинального метода. Затем прокси-механизм передает вызов оригинальному объекту, который фактически отправляет письмо. После выполнения метода отправки письма прокси-механизм может выполнить дополнительные действия, например, записать информацию о том, что письмо было успешно отправлено. Таким образом, Dynamic Proxy дает возможность добавлять дополнительное поведение к существующим классам или интерфейсам, не изменяя их код. Это полезно, когда вы хотите добавить функциональность, такую как логирование, мониторинг или проверку безопасности, без вмешательства в сам класс или интерфейс.»

Источник

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

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

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

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

  • логирование старта и остановки метода;
  • дополнительную проверку аргументов;
  • имитацию поведения исходного класса;
  • реализацию отложенной инициализации затратных ресурсов;

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

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

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

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

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

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

Самое простое — использовать java.lang.reflect.Proxy , который является частью JDK. Этот класс может создать прокси-класс или напрямую его инстанс. Пользоваться прокси, встроенным в Java, очень просто. Все что нужно — реализовать java.lang.InvocationHandler , чтобы прокси-объект мог его вызывать. Интерфейс InvocationHandler крайне прост и содержит только один метод: invoke() . При его вызове, аргументы содержат проксируемый оригинальный объект, вызванный метод (как отражение объекта Method ) и массив объектов исходных аргументов. Фрагмент кода ниже демонстрирует применение:

package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyDemo < interface If < void originalMethod(String s); >static class Original implements If < public void originalMethod(String s) < System.out.println(s); >> static class Handler implements InvocationHandler < private final If original; public Handler(If original) < this.original = original; >public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException < System.out.println("BEFORE"); method.invoke(original, args); System.out.println("AFTER"); return null; >> public static void main(String[] args)< Original original = new Original(); Handler handler = new Handler(original); If f = (If) Proxy.newProxyInstance(If.class.getClassLoader(), new Class[] < If.class >, handler); f.originalMethod("Hallo"); > >

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

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

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

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

Источник

Читайте также:  Ajax post success html
Оцените статью