Что такое прокси в Spring?
4. AopUtils.getTargetClass() → для рефлексии 5. Избегайте final на проксируемых методах 6. Spring Boot → CGLIB по умолчанию
🟢 Junior Level
Прокси — объект-посредник, который Spring создаёт вокруг вашего бина.
Простая аналогия: Секретарь. Вы хотите поговорить с директором, но сначала проходите через секретаря, который проверяет ваши документы, записывает на приём и т.д.
@Service
public class UserService {
@Transactional // ← Spring создаст Proxy вокруг этого бина
public void save(User user) { }
}
// Вы получаете не UserService, а Proxy!
// Proxy → открывает транзакцию → вызывает save() → закрывает транзакцию
Зачем:
- Добавляет функциональность без изменения вашего кода
- @Transactional, @Async, @Cacheable — всё через прокси
🟡 Middle Level
Два типа прокси
JDK Dynamic Proxy:
// Через интерфейсы
public interface UserService { void save(User user); }
// Spring создаёт:
class UserServiceProxy implements UserService {
private UserService target;
public void save(User user) {
// До вызова
target.save(user);
// После вызова
}
}
CGLIB:
// Через наследование
class UserServiceProxy extends UserService {
// Переопределяет методы
}
// ❌ Не может проксировать final классы/методы!
Spring Boot по умолчанию
Spring Boot 2.x/3.x: proxy-target-class=true по умолчанию, поэтому CGLIB используется всегда, даже если есть интерфейсы. Spring 1.x: JDK Dynamic Proxy по умолчанию.
Почему:
→ Меньше проблем с типами
→ Можно инжектить по классу, не по интерфейсу
Само-вызов (Self-invocation)
@Service
public class UserService {
public void outer() {
inner(); // ❌ @Transactional НЕ сработает!
}
@Transactional
public void inner() { }
}
// outer() → inner() идёт напрямую, мимо прокси!
🔴 Senior Level
Цепочка интерцепторов
Вызов метода на прокси:
1. SecurityInterceptor → проверка прав
2. TransactionInterceptor → открытие транзакции
3. CacheInterceptor → проверка кэша
4. Target method → ваш код
5. Commit/Rollback
6. Возврат результата
→ Каждый интерцептор = один Advice
→ Порядок определяется @Order
Как проверить наличие прокси
// В дебаггере:
// JDK: com.sun.proxy.$Proxy123
// CGLIB: UserService$$EnhancerBySpringCGLIB$$abc123
// Программно:
AopUtils.isAopProxy(bean);
AopUtils.isCglibProxy(bean);
AopUtils.getTargetClass(proxy); // когда вы работаете с объектом, но не знаете, прокси это или оригинал (например, при сравнении типов через `instanceof` или `getDeclaredMethod`).
Production Experience
Реальный сценарий: final метод не проксируется
@Service
public class UserService {
@Transactional
public final void save(User user) { } // final!
}
// CGLIB создаёт подкласс и переопределяет методы.
// `final` методы переопределить нельзя, поэтому вызов идёт
// напрямую — мимо логики прокси (транзакций, логирования и т.д.)
// Транзакция НЕ создаётся!
// → Данные сохраняются БЕЗ транзакции
// → Баг в production!
Best Practices
- JDK Proxy → через интерфейсы
- CGLIB → через наследование (не final!)
- Self-invocation → транзакция не работает
- AopUtils.getTargetClass() → для рефлексии
- Избегайте final на проксируемых методах
- Spring Boot → CGLIB по умолчанию
Резюме для Senior
- Прокси = обёртка для AOP
- JDK = интерфейсы, CGLIB = наследование
- Spring Boot → CGLIB всегда
- Self-invocation = вызов мимо прокси
- final методы → не проксируются CGLIB
- Цепочка интерцепторов → порядок через @Order
🎯 Шпаргалка для интервью
Обязательно знать:
- Прокси = объект-обёртка, добавляющий AOP-функциональность без изменения кода
- Два типа: JDK Dynamic Proxy (через интерфейсы) и CGLIB (через наследование)
- Spring Boot 2.x/3.x: proxy-target-class=true по умолчанию → всегда CGLIB
- @Transactional, @Async, @Cacheable — все работают через прокси
- Self-invocation: вызов
this.method()идёт напрямую, минуя прокси - final-классы и final-методы НЕ могут быть проксированы через CGLIB
- Цепочка интерцепторов: Security → Transaction → Cache → Target method
Частые уточняющие вопросы:
- Как проверить, является ли бин прокси? → AopUtils.isAopProxy(bean), AopUtils.isCglibProxy(bean)
- Почему self-invocation не работает? → this ссылается на Target, а не на Proxy
- Как получить оригинальный класс из прокси? → AopUtils.getTargetClass(proxy)
- В чём разница JDK Proxy и CGLIB? → JDK требует интерфейс, CGLIB создаёт подкласс
Красные флаги (НЕ говорить):
- «CGLIB может проксировать final-методы» — не может, наследование невозможно
- «Self-invocation работает с CGLIB» — не работает, this = Target
- «Прокси добавляет значительный overhead» — наносекунды, пренебрежимо
- «Spring создаёт прокси для каждого бина» — только с AOP-аннотациями
Связанные темы:
- [[14. Когда Spring создаёт прокси]]
- [[15. Что такое AOP (Aspect-Oriented Programming)]]
- [[18. Почему @Transactional не работает при self-invocation]]
- [[19. Как решить проблему с self-invocation]]