Как работает Spring AOP: прокси-объекты (JDK Proxy и CGLIB)
Spring AOP не изменяет исходный код класса.
Он создает прокси-объект, который «перехватывает» вызовы методов и добавляет дополнительное поведение (аспекты).
Используются два механизма:
• JDK Dynamic Proxy — если класс реализует интерфейс;
• CGLIB Proxy — если интерфейсов нет, создается подкласс.
Прокси вызывает аспекты до и после основного метода, «вплетая» дополнительную логику во время выполнения.
Подробное объяснение
Spring AOP реализует аспектное программирование без изменения байт-кода.
Когда контейнер создает бин, он не возвращает исходный объект, а создает его обертку — прокси.
Прокси перехватывает каждый вызов метода и выполняет нужные аспекты в нужный момент.
Затем прокси передает управление реальному объекту (целевому бину).
1. Что такое прокси
Прокси (proxy) — это объект-посредник, который имеет тот же интерфейс, что и реальный класс, но при вызове методов выполняет дополнительный код.
Схематично:
Клиент → Прокси → Реальный объект
Клиент думает, что вызывает метод сервиса напрямую, но на самом деле обращается к прокси, который:
Проверяет, подходит ли вызов под pointcut;
Выполняет соответствующие советы (@Before, @Around, @AfterReturning и т. д.);
После этого вызывает реальный метод.
2. JDK Dynamic Proxy
Если класс реализует хотя бы один интерфейс, Spring использует динамический прокси JDK.
Он создает объект, реализующий тот же интерфейс, и делегирует вызовы через специальный обработчик (InvocationHandler).
Пример (упрощенно):
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
(proxyObj, method, args) -> {
System.out.println("До вызова метода " + method.getName());
Object result = method.invoke(userService, args);
System.out.println("После вызова метода " + method.getName());
return result;
});
proxy.createUser("Ivan");
Вывод:
До вызова метода createUser
Пользователь Ivan создан
После вызова метода createUser
Здесь Proxy.newProxyInstance() создал новый объект, который перехватывает вызовы и вставляет дополнительный код — аналогично тому, как это делает Spring AOP.
3. CGLIB Proxy
Если класс не реализует интерфейсов, Spring использует библиотеку CGLIB (Code Generation Library).
Она создает динамический подкласс (subclass), который наследуется от исходного класса и переопределяет его методы.
Пример:
@Service
public class PaymentService {
public void pay() {
System.out.println("Выполнение платежа");
}
}
Spring создаст прокси PaymentService$$EnhancerBySpringCGLIB, который расширяет PaymentService и добавляет обертку вокруг метода pay():
@Override
public void pay() {
// аспект до
super.pay();
// аспект после
}
Для разработчика все выглядит как обычный вызов paymentService.pay(),
но на деле метод проходит через динамически сгенерированный код.
4. Как Spring выбирает тип прокси
Spring автоматически определяет, какой тип прокси создать:
если бин реализует хотя бы один интерфейс → JDK Dynamic Proxy;
если интерфейсов нет → CGLIB Proxy.
Можно явно задать стратегию в конфигурации:
@EnableAspectJAutoProxy(proxyTargetClass = true)
Параметр proxyTargetClass = true заставит Spring всегда использовать CGLIB, даже если есть интерфейс.
5. Ограничения и особенности
AOP работает только при вызове через прокси.
Если метод вызывает сам себя изнутри класса, аспект не сработает, потому что вызов идет напрямую, минуя прокси.
Финальные методы и классы не могут быть проксированы через CGLIB,
так как CGLIB использует наследование.
Прокси создаются при старте контекста Spring,
поэтому аспекты не влияют на объекты, созданные вручную через new.
Итог:
Spring не встраивает аспекты в код напрямую, а создает прокси-обертки,
которые перехватывают вызовы методов и вставляют дополнительные действия.
Это безопасно, не требует изменения исходного кода и работает динамически —
все аспекты действуют только там, где указано в pointcut-выражениях.
