Рубрики
Без рубрики

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

Узнайте, как использовать динамические прокси Java – один из основных механизмов прокси, доступных в языке.

Автор оригинала: baeldung.

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 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 .