Proxy (Прокси)

Последнее обновление: 04.10.2025

Proxy (Прокси) в языке Java применяются для создания таких объектов, которые действуют как экземпляры интерфейсов, но при этом допускают настройку вызова методов этих интерфейсов. Прокси представляют класс java.lang.reflect.Proxy и не являются часто используемым инструментом и, как привило, применяются в системном программирования, например, при создании плагинов. В частности, прокси можно использовать, чтобы во время выполнения программы создать новые классоы, которые реализуют определеный набор интерфейсов. Прокси-классы могут использоваться для перенаправления вызовов методов на удаленные серверы, для связывания событий пользовательского интерфейса с некоторыми действиями, для отслеживание вызовов методов для отладки

Прокси-класс, как и все другие, имеет методы, унаследованные от базового класса Object (как toString(), equals() и тд.). Но кроме того прокси-класс также имеет методы, которые определены в применяемых интерфейсах.

Обработчик вызовов (invocation handler)

Главное отличие по сравнению со стандартной реализацией интерфейсов заключается в том, что во время выполнения нельзя определить новый код для методов реализуемых интерфейсов. Вместо этого необходимо предоставить обработчик вызовов (invocation handler). Обработчик вызовов представляет объект произвольного класса, который реализует интерфейс InvocationHandler. Этот интерфейс имеет единственный метод:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

Метод принимает следующие параметры:

  • proxy: объект прокси-класса, на котором был вызван метод

  • method: экземпляр метода, который соответствует методу вызываемого интерфейса

  • args: массив аргументов, передаваемых в вызываемый метод при вызове, или null, если метод интерфейса не принимает аргументов. Аргументы примитивных типов обертываются в соответствующие класса-обертки, например, int в java.lang.Integer или boolean в java.lang.Boolean.

Метод возвращает значение, возвращаемое классом-прокси при вызове метода реализуемого интерфейса.

В итоге, при каждом вызове метода прокси-объекта у обработчика вызовов вызывается метод invoke(), в который передается объект Method и аргументы для методы.

Рассмотрим небольшой пример. Допустим, мы хотим отслеживать вызовы методов. И для этого определим следующий класс:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

class DebugHandler implements InvocationHandler{

   private Object target;

   DebugHandler(Object target){

      this.target = target;
   }

   public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {

      // выводим имя аргумента
      System.out.print(target);
      // выводим имя метода
      System.out.print("." + m.getName() + "(");
      // выводим аргументы
      if (args != null) {

         for (int i = 0; i < args.length; i++){

            if (i != args.length - 1) System.out.print(", ");
            System.out.print(args[i]);
         }
      }
      System.out.println(")");

      // вызываем метод интерфейса
      return m.invoke(target, args);
   }
}

Здесь определяется класс-реализация интерфейса InvokeHandler - класс DebugHandler. Через конструктор он принимает объект, на котором вызывается метод реализуемого интерфейса. А его метод invoke() просто выводит имя и аргументы вызываемого метода, а затем вызывает этот метод, передавая в него объект и аргументы.

Создание прокси-объекта

Для создания прокси-объекта применяется статический метод Proxy.newProxyInstance():

static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

Этот метод принимает три параметра:

  • loader: загрузчик классов для определения прокси-класса. В качестве загрузчика можно использовать системный загрузчик, который можно получить с помощью метода ClassLoader.getSystemClassLoader()

  • interfaces: список интерфейсов, реализуемых прокси-классом

  • h: обработчик вызовов для диспетчеризации вызовов методов

Рассмотрим, как создать и использовать прокси-объект:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class Program{
     
	public static void main(String[] args) {
         
        Object start = new Point(10, 15);
        var handler = new DebugHandler(start);
        Movable proxy = (Movable) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] { Movable.class }, handler);

        proxy.move(25);
        proxy.move(15);
        proxy.move(10);
    }
}

class Point implements Movable{

    private int x;
    private int y;

    Point(int x, int y){

        this.x = x;
        this.y = y;
    }
    @Override
    public String toString(){ return "(" + x + ", " + y + ")";}

    public void move(int distance){

        System.out.printf("Перемещаем точку (%d, %d) на %d пикселей\n\n", x, y, distance);
        x += distance;
        y += distance;
    }
}

interface Movable{
    void move(int distance);
}

class DebugHandler implements InvocationHandler{

   private Object target;

   public DebugHandler(Object target){

      this.target = target;
   }

   public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {

      // выводим имя аргумента
      System.out.print(target);
      // выводим имя метода
      System.out.print("." + m.getName() + "(");
      // выводим аргументы
      if (args != null) {

         for (int i = 0; i < args.length; i++){

            if (i != args.length - 1) System.out.print(", ");
            System.out.print(args[i]);
         }
      }
      System.out.println(")");

      // вызываем метод интерфейса
      return m.invoke(target, args);
   }
}

Итак, у нас есть некоторый интерфейс Movable с одним методом move():

interface Movable{
    void move(int distance);
}

Метод move() в качестве параметра принимает условное расстояние, на которое надо переместить объект.

Есть класс Point, который представляет точку на плоскости и реализует этот интерфейс:

class Point implements Movable{

    private int x;
    private int y;

    Point(int x, int y){

        this.x = x;
        this.y = y;
    }
    @Override
    public String toString(){ return "(" + x + ", " + y + ")";}

    public void move(int distance){

        System.out.printf("Перемещаем точку (%d, %d) на %d пикселей\n\n", x, y, distance);
        x += distance;
        y += distance;
    }
}

В реализации метода move() просто прибавляем расстояние distance к координатам x и y у точки и выводим некоторое диагностическое сообщение.

Теперь посмотрим на применение прокси-объекта в методе main. Но прежде всего создаем объект Point, для которого будет затем вызываться метод move()

Object start = new Point(10, 15);

Затем создаем объект нашего обработчика вызовов - класса DebugHandler, которому передаем целевой объект:

var handler = new DebugHandler(start);

И далее создаем сам прокси-объект:

Movable proxy = (Movable) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] { Movable.class }, handler);

В данном случае прокси-объект будет вести себя как объект интерфейса Movable, поэтому преобразуем объект к этому интерфейсу.

Для создания прокси в качестве загрузчика применяем системный загрузчик, который получаем с помощью вызова ClassLoader.getSystemClassLoader()

В качестве второго параметра передаем массив отслеживаемых классов интерфейсов. В нашем случае у нас один интерфейс - Movable:

new Class[] { Movable.class }

И третий параметр - наш обработчик вызовов handler.

Итак, мы создали прокси-объект, который ведет себя как объект интерфейса Movable. А обработчик вызовов может перехватить вызовы методов этого интерфейса. Далее для демонстрации несколько раз вызываем на прокси-объекте метод move():

proxy.move(25);
proxy.move(15);
proxy.move(10);

При каждом вызове proxy.move() обработчик вызовов через метод invoke() получит вызванный метод и его аргументы, выведет данные на консоль и затем вызовет этот метод на объекте Point с помощью выражения

m.invoke(target, args)

Таким образом, выражение

proxy.move(25)

фактически превратится в

start.move(25); // где start = new Point(10, 15)

В итоге мы получим следующий консольный вывод:

(10, 15).move(25)
Перемещаем точку (10, 15) на 25 пикселей

(35, 40).move(15)
Перемещаем точку (35, 40) на 15 пикселей

(50, 55).move(10)
Перемещаем точку (50, 55) на 10 пикселей
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.