Що таке proxy в 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 створює proxy]]
- [[15. Що таке AOP (Aspect-Oriented Programming)]]
- [[18. Чому @Transactional не працює при self-invocation]]
- [[19. Як вирішити проблему з self-invocation]]