Как паттерн Proxy заменяет оригинальный объект в Java?

«Как паттерн Proxy заменяет оригинальный объект в Java?» — вопрос из категории Паттерны, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн Proxy (Заместитель) предоставляет объект-субститут, который контролирует доступ к реальному объекту. Клиент взаимодействует с прокси, который делегирует вызовы целевому объекту, добавляя при необходимости свою логику.

Ключевой механизм: Прокси реализует тот же интерфейс, что и оригинальный объект, что позволяет ему быть его полной заменой.

Пример: Динамический прокси в Java (логирование вызовов)

import java.lang.reflect.*;

// 1. Общий интерфейс
interface DatabaseService {
    String fetchData(String query);
}

// 2. Реальный объект
class RealDatabaseService implements DatabaseService {
    public String fetchData(String query) {
        return "Result for: " + query;
    }
}

// 3. Создание прокси с обработчиком вызовов
InvocationHandler loggingHandler = new InvocationHandler() {
    private final RealDatabaseService realService = new RealDatabaseService();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[LOG] Calling method: " + method.getName());
        // Дополнительная логика: проверка прав, кеширование, замер времени
        Object result = method.invoke(realService, args); // Делегирование вызова
        System.out.println("[LOG] Method finished.");
        return result;
    }
};

// 4. Создание экземпляра прокси
DatabaseService proxyInstance = (DatabaseService) Proxy.newProxyInstance(
        DatabaseService.class.getClassLoader(),
        new Class[]{DatabaseService.class}, // Интерфейс, который реализует прокси
        loggingHandler
);

// Клиентский код работает с прокси
String data = proxyInstance.fetchData("SELECT * FROM users");
// Вывод: [LOG] Calling method: fetchData
//        [LOG] Method finished.

Типы прокси в Java:

  • Динамические прокси (как в примере): Создаются во время выполнения для интерфейсов.
  • Прокси на основе наследования (CGLIB): Создают подкласс для классов (не только интерфейсов).
  • Виртуальные прокси: Ленивая инициализация тяжёлого объекта.
  • Защищающие прокси (Protection Proxy): Контроль прав доступа.