0Pricing
Java Academy · Урок

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

Создавайте динамические прокси JDK для перехвата вызовов методов интерфейса при журналировании, кэшировании и повторных попытках.

«Динамические прокси с InvocationHandler» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Что такое динамический прокси

Динамический прокси JDK — это объект, созданный во время выполнения и реализующий один или несколько интерфейсов. Все вызовы методов передаются предоставленному вами InvocationHandler.

Создание прокси с помощью Proxy.newProxyInstance

Вызовите Proxy.newProxyInstance(classLoader, interfaces[], handler), чтобы создать прокси. Прокси реализует все перечисленные интерфейсы.

UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new LoggingHandler(realService)
);
proxy.findById(42L); // intercepted by LoggingHandler

Интерфейс InvocationHandler

Реализуйте InvocationHandler, чтобы обрабатывать каждый вызов метода. Обработчик получает объект прокси, вызванный метод и аргументы.

public class LoggingHandler implements InvocationHandler {
    private final Object target;
    public LoggingHandler(Object target) { this.target = target; }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">> " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("<< " + method.getName());
        return result;
    }
}

Прокси для кэширования

InvocationHandler может кэшировать результаты: если кэш содержит результат для этих аргументов, верните его; в противном случае вызовите настоящий метод и сохраните результат.

public class CachingHandler implements InvocationHandler {
    private final Object target;
    private final Map<String, Object> cache = new HashMap<>();
    public CachingHandler(Object target) { this.target = target; }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String key = method.getName() + Arrays.toString(args);
        return cache.computeIfAbsent(key, k -> {
            try { return method.invoke(target, args); }
            catch (Exception e) { throw new RuntimeException(e); }
        });
    }
}

Прокси для логики повторных попыток

Оберните вызовы методов в цикл повторных попыток. Если возникает исключение, повторяйте попытку до N раз, прежде чем выбросить исключение снова.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    for (int attempt = 1; attempt <= 3; attempt++) {
        try { return method.invoke(target, args); }
        catch (InvocationTargetException e) {
            if (attempt == 3) throw e.getCause();
            Thread.sleep(1000L * attempt);
        }
    }
    throw new RuntimeException("unreachable");
}

Прокси для управления транзакциями

@Transactional в Spring использует динамический прокси, чтобы начать транзакцию перед вызовом метода и зафиксировать или откатить её после — и всё это незаметно для вызывающего кода.

Проверка, является ли объект прокси

Используйте Proxy.isProxyClass(obj.getClass()), чтобы обнаружить прокси, а Proxy.getInvocationHandler(proxy) — чтобы получить обработчик.

if (Proxy.isProxyClass(service.getClass())) {
    InvocationHandler h = Proxy.getInvocationHandler(service);
    System.out.println("Handler: " + h.getClass().getSimpleName());
}

Ограничения динамических прокси JDK

Динамические прокси JDK работают только с интерфейсами — они не могут проксировать конкретные классы. Для проксирования классов используйте CGLIB, который Spring также применяет внутри для классов @Configuration.

CGLIB как альтернатива

CGLIB во время выполнения создаёт подкласс целевого класса, переопределяя все методы, кроме final. Spring использует его, если интерфейсы отсутствуют. Аннотация не требуется — механизм прозрачен.

Прокси в Hibernate

Hibernate загружает связи сущностей по требованию с помощью прокси CGLIB/Byte Buddy. Когда вы обращаетесь к ленивой коллекции, прокси выполняет SQL-запрос.

// Hibernate creates a proxy for:
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
// Accessing dept triggers a DB query via the proxy

Производительность прокси

Диспетчеризация методов динамического прокси примерно в 5–10 раз медленнее прямого вызова. Для особенно часто выполняемых участков рассмотрите генерацию кода во время компиляции (APT) или MethodHandles вместо динамических прокси.

Быстрая проверка

Каково главное ограничение динамических прокси JDK?

Повторение

Динамические прокси JDK перехватывают вызовы методов интерфейсов через InvocationHandler. Используйте их для журналирования, кэширования, повторных попыток и управления транзакциями. Для проксирования классов используйте CGLIB. Кэшируйте создание прокси — оно основано на рефлексии и не является бесплатным.

Часто задаваемые вопросы

Урок «Динамические прокси с InvocationHandler» бесплатный?

Да — полный текст урока «Динамические прокси с InvocationHandler» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Динамические прокси с InvocationHandler»?

Создавайте динамические прокси JDK для перехвата вызовов методов интерфейса при журналировании, кэшировании и повторных попытках. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Динамические прокси с InvocationHandler»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Инспектирование классов с помощью рефлексии
  2. Вызов методов и доступ к полям
  3. Динамические прокси с InvocationHandler
  4. Производительность рефлексии и альтернативы
← Назад к Java Academy