История вопроса:
Прокси-объекты и паттерн Proxy появились в Java для создания объектов-заместителей, которые могут контролировать доступ к реальному объекту, изменять его поведение или внедрять кросс-секционные функции (security, logging, metrics). Начиная с Java 1.3, появилась поддержка стандартных dynamic proxies в пакете java.lang.reflect, а позднее — популярные библиотеки CGLIB и ByteBuddy.
Проблема:
Не всегда возможно/удобно изменять логику существующего класса напрямую. Часто требуется прозрачно добавить поведение (например, логирование, кэширование, транзакции) без изменения исходного кода объекта, что невозможно средствами стандартного наследования.
Решение:
Proxy-объекты могут быть реализованы статически (через ручной subclassing) и динамически (через механизм рефлексии или байткода). Динамический proxy позволяет на лету создавать объекты, реализующие нужный интерфейс и делегирующие вызовы реальному объекту внутри метода invoke(), предоставляя гибкость для внедрения стороннего поведения.
Пример кода:
import java.lang.reflect.*; interface Service { void doWork(); } class RealService implements Service { public void doWork() { System.out.println("Doing work!"); } } class LoggingInvocationHandler implements InvocationHandler { private final Object target; public LoggingInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling: " + method.getName()); return method.invoke(target, args); } } Service real = new RealService(); Service proxy = (Service) Proxy.newProxyInstance( real.getClass().getClassLoader(), new Class[] {Service.class}, new LoggingInvocationHandler(real)); proxy.doWork(); // Логгируется вызов
Ключевые особенности:
Может ли стандартный динамический Proxy в Java работать с классами, не реализующими интерфейсы?
Нет. Стандартный Proxy в Java работает только с интерфейсами. Для классов без интерфейсов (например, если вы хотите проксировать обычный класс) нужны сторонние средства (например, CGLIB, ByteBuddy).
Может ли быть создан proxy для интерфейса с приватными методами?
Нет. Интерфейсы в Java не могут содержать приватные методы, которые бы требовалось проксировать. Динамический proxy реализует публичные методы интерфейса. Начиная с Java 9 появились private default методы, но они не доступны через proxy.
В чем разница между InvocationHandler и MethodInterceptor (CGLIB)?
InvocationHandler — стандартный интерфейс JDK, используемый для dynamic proxies. Он принимает вызовы методов интерфейса. MethodInterceptor — интерфейс CGLIB, позволяющий перехватывать вызовы методов в любых классах через динамическое наследование. Отличие в применимости и уровне: JDK работает только с интерфейсами, CGLIB — с любыми классами.
Инженер вручную повторяет код логирования в каждом методе всех сервисов. При добавлении новых методов постоянно дублирует логику, часто забывая добавить нужные вызовы.
Плюсы:
Минусы:
В проект внедрён AOP через динамический proxy: логирование и контроль транзакций осуществляется через обёртки-заместители, реализуемые централизованно.
Плюсы:
Минусы: