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

Який тип ін'єкції рекомендується і чому?

4. Field Injection — не рекомендований для production коду. У тестах допустимий (тестові класи часто не тестують на DI-патерни). 5. > 5 параметрів → порушення SRP 6. Конструктор...

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

🟢 Junior Level

Рекомендується: Constructor Injection!

// ✅ Правильно
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository repo;
}

// ❌ Неправильно
@Service
public class OrderService {
    @Autowired private OrderRepository repo;  // Field injection
}

Чому:

  1. Можна зробити поле final
  2. Додаток впаде одразу, якщо залежності немає
  3. Легко тестувати без Spring

🟡 Middle Level

Переваги Constructor Injection

1. Імутабельність:

private final Repo repo;  // Не можна змінити після створення

2. Fail-fast:

Constructor: помилка під час старту, якщо залежності немає
Field: об'єкт створиться, NPE при першому виклику методу

3. Тестування:

// Constructor: тест без Spring
OrderService service = new OrderService(mockRepo);

// Field: потрібен @SpringBootTest або рефлексія

4. Цикли:

Constructor → помилка одразу → лагодьте архітектуру
Field → працює → баги в рантаймі

Коли Spring обирає конструктор

// Один конструктор → Spring використовує його автоматично
public Service(Repo repo) { }  // @Autowired не потрібен!

// Кілька конструкторів:
// 1. Шукає @Autowired
// 2. Якщо немає → конструктор за замовчуванням
// 3. Якщо немає → помилка

Проблема циків

// ❌ A → B → A через конструктори
// Spring не може створити жоден → помилка

// ✅ Рефакторинг: виділити спільний сервіс C
// A → C, B → C (немає циклу!)

// ⚠️ Костиль: @Lazy
public Service(@Lazy Dependency dep) { }
// → Spring впровадить проксі, реальна залежність при першому виклику

Коли Constructor Injection НЕ оптималний

  1. Занадто багато залежностей (10+) — ознака того, що клас робить занадто багато (порушення SRP). Розбийте на менші класи.
  2. Циклічні залежності — Spring не зможе створити біни. Використовуйте setter injection або @Lazy.
  3. Успадкування з обов’язковими залежностями — дочірній клас повинен передати залежності батька через super().

🔴 Senior Level

Конфлікт з CGLIB Proxy

Spring AOP створює проксі через підклас:
  1. new Service(...) → реальний об'єкт
  2. CGLIB підклас → проксі

→ Конструктор виконується ДВІЧІ!
→ Важка логіка в конструкторі = проблема

Рішення:
  → Конструктор = лише присвоєння полів
  → Ініціалізація → в @PostConstruct

Under the hood: як Spring впроваджує

AutowiredAnnotationBeanPostProcessor:
  1. Знайшов конструктор з @Autowired (або єдиний)
  2. Вирішив залежності через BeanFactory
  3. Викликав конструктор

→ Reflection → overhead
→ Constructor Injection трохи швидший під час старту (один конструктор vs усі поля). На практиці різниця мінімальна, головна причина — безпека та тестованість.

Production Experience

Реальний сценарій: Field Injection приховав 20 циклів

  • Додаток стартував, але падав рандомно
  • 4 години дебагу → знайшли циклічні залежності
  • Перейшли на Constructor Injection → помилка на старті → полагодии за 10 хвилин

Best Practices

  1. Constructor — завжди за замовчуванням
  2. @RequiredArgsConstructor — Lombok для зручності
  3. @Lazy — не лише для циклічних залежностей. Корисний також для відкладеного завантаження важких бінів (економія пам’яті і часу старту).
  4. Field Injection — не рекомендований для production коду. У тестах допустимий (тестові класи часто не тестують на DI-патерни).
  5. > 5 параметрів → порушення SRP
  6. Конструктор = лише присвоєння полів

Резюме для Senior

  • Constructor = імутабельність, fail-fast, тести
  • Field = технічний борг
  • Цикли → Constructor ламається одразу (добре!)
  • CGLIB → конструктор викликається двічі
  • @Lazy → проксі-заглушка, реальна при виклику
  • SRP → > 5 залежностей = розбийте клас

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Рекомендований тип — Constructor Injection: імутабельність (final), fail-fast, зручне тестування без Spring
  • Constructor Injection робить залежності явними — видно, що потрібно класу
  • За відсутності залежності додаток впаде на старті, а не в рантаймі (NPE)
  • Тестування без Spring: new Service(mockRepo) — швидко і просто
  • Циклічні залежності через constructor ламають старт — це добре, одразу видна проблема
  • Конструктор повинен містити лише присвоєння полів, вся логіка — в @PostConstruct
  • @Lazy вирішує цикли через проксі-заглушку, але це костиль — краще рефакторинг
  • 5 параметрів конструктора → порушення SRP, розбийте клас

Часті уточнюючі запитання:

  • Чому @Lazy — костиль? Він масковує циклічні залежності, не усуваючи архітектурну проблему.
  • Чому конструктор виконується двічі з CGLIB? Spring створює реальний об’єкт + підклас-проксі, обидва викликають конструктор.
  • Як Spring обирає конструктор? Якщо конструктор один — автоматично. Якщо кілька — шукає @Autowired.
  • Коли Constructor Injection НЕ оптималний? Занадто багато залежностей (10+), цикли, успадкування з обов’язковими залежностями.

Червоні прапорці (НЕ говорити):

  • «Field Injection допустимий у production коді» (не рекомендований, лише в тестах)
  • «@Lazy — правильне рішення для циклів» (костиль, краще рефакторинг)
  • «Чим більше залежностей, тим краще — клас потужніший» (порушення SRP, розбийте на частини)
  • «Constructor Injection повільніший через Reflection» (різниця в мілісекундах, головне — безпека)

Пов’язані теми:

  • [[1. Що таке Dependency Injection]]
  • [[2. В чому різниця між constructor, setter та field injection]]
  • [[6. Що таке Bean Lifecycle]]
  • [[7. Які етапи є у Bean lifecycle]]
  • [[9. Що роблять методи з анотаціями @PostConstruct та @PreDestroy]]