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"); // exception5. Пример Динамического Прокси-сервера Синхронизации
Давайте рассмотрим один потенциальный реальный сценарий для динамических прокси-серверов.
Предположим, мы хотим записать, сколько времени занимает выполнение наших функций. В этой степени мы сначала определяем обработчик, способный обертывать “реальный” объект, отслеживать информацию о времени и рефлексивный вызов:
public class TimingDynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
TimingDynamicInvocationHandler.class);
private final Map methods = 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 .