1. введение
Эта статья посвящена динамическим прокси-серверам Java , которые являются одним из основных прокси – механизмов, доступных нам в языке.
Проще говоря, прокси – серверы-это фронты или оболочки, которые передают вызов функции через свои собственные средства (обычно на реальные методы) – потенциально добавляя некоторую функциональность.
Динамические прокси позволяют одному классу с одним методом обслуживать несколько вызовов методов произвольным классам с произвольным числом методов. Динамический прокси-сервер можно рассматривать как своего рода Фасад , но такой, который может претендовать на реализацию любого интерфейса. Под крышкой он направляет все вызовы методов в один обработчик – метод invoke () .
Хотя это не инструмент, предназначенный для повседневных задач программирования, динамические прокси могут быть весьма полезны для разработчиков фреймворков. Он также может использоваться в тех случаях, когда конкретные реализации классов не будут известны до выполнения.
Эта функция встроена в стандартный JDK, поэтому никаких дополнительных зависимостей не требуется.
2. Обработчик вызова
Давайте построим простой прокси-сервер, который на самом деле ничего не делает, кроме печати того, какой метод был запрошен для вызова, и возвращает жестко закодированное число.
Во-первых, нам нужно создать подтип java.lang.reflect.InvocationHandler :
public class DynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( DynamicInvocationHandler.class); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LOGGER.info("Invoked method: {}", method.getName()); return 42; } }
Здесь мы определили простой прокси-сервер, который регистрирует, какой метод был вызван, и возвращает 42.
3. Создание Экземпляра Прокси-сервера
Экземпляр прокси-сервера, обслуживаемый обработчиком вызова, который мы только что определили, создается с помощью вызова заводского метода в классе java.lang.reflect.Proxy :
Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new DynamicInvocationHandler());
Как только у нас есть экземпляр прокси-сервера, мы можем вызывать его методы интерфейса как обычно:
proxyInstance.put("hello", "world");
Как и ожидалось, в файле журнала выводится сообщение о вызываемом методе put () .
4. Обработчик вызова с помощью лямбда-выражений
С тех пор InvocationHandler – это функциональный интерфейс, можно определить обработчик встроенным с помощью лямбда-выражения:
Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, (proxy, method, methodArgs) -> { if (method.getName().equals("get")) { return 42; } else { throw new UnsupportedOperationException( "Unsupported method: " + method.getName()); } });
Здесь мы определили обработчик, который возвращает 42 для всех операций get и выдает UnsupportedOperationException для всего остального.
Он вызывается точно таким же образом:
(int) proxyInstance.get("hello"); // 42 proxyInstance.put("hello", "world"); // exception
5. Пример Динамического Прокси-сервера Синхронизации
Давайте рассмотрим один потенциальный реальный сценарий для динамических прокси-серверов.
Предположим, мы хотим записать, сколько времени занимает выполнение наших функций. В этой степени мы сначала определяем обработчик, способный обертывать “реальный” объект, отслеживать информацию о времени и рефлексивный вызов:
public class TimingDynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( TimingDynamicInvocationHandler.class); private final Mapmethods = new HashMap<>(); private Object target; public TimingDynamicInvocationHandler(Object target) { this.target = target; for(Method method: target.getClass().getDeclaredMethods()) { this.methods.put(method.getName(), method); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.nanoTime(); Object result = methods.get(method.getName()).invoke(target, args); long elapsed = System.nanoTime() - start; LOGGER.info("Executing {} finished in {} ns", method.getName(), elapsed); return result; } }
Впоследствии этот прокси-сервер может использоваться для различных типов объектов:
Map mapProxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new TimingDynamicInvocationHandler(new HashMap<>())); mapProxyInstance.put("hello", "world"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { CharSequence.class }, new TimingDynamicInvocationHandler("Hello World")); csProxyInstance.length()
Здесь мы проксировали карту и последовательность символов (строку).
Вызовы прокси-методов будут делегироваться обернутому объекту, а также создавать инструкции ведения журнала:
Executing put finished in 19153 ns Executing get finished in 8891 ns Executing charAt finished in 11152 ns Executing length finished in 10087 ns
6. Заключение
В этом кратком руководстве мы рассмотрели динамические прокси Java, а также некоторые из его возможных применений.
Как всегда, код в примерах можно найти на GitHub .