Питання 13 · Розділ 5

Що таке proxy в Spring?

4. AopUtils.getTargetClass() → для рефлексії 5. Уникайте final на проксованих методах 6. Spring Boot → CGLIB за замовчуванням

Мовні версії: English Russian Ukrainian

🟢 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

  1. JDK Proxy → через інтерфейси
  2. CGLIB → через успадкування (не final!)
  3. Self-invocation → транзакція не працює
  4. AopUtils.getTargetClass() → для рефлексії
  5. Уникайте final на проксованих методах
  6. 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]]