Який тип ін'єкції рекомендується і чому?
4. Field Injection — не рекомендований для production коду. У тестах допустимий (тестові класи часто не тестують на DI-патерни). 5. > 5 параметрів → порушення SRP 6. Конструктор...
🟢 Junior Level
Рекомендується: Constructor Injection!
// ✅ Правильно
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo;
}
// ❌ Неправильно
@Service
public class OrderService {
@Autowired private OrderRepository repo; // Field injection
}
Чому:
- Можна зробити поле
final - Додаток впаде одразу, якщо залежності немає
- Легко тестувати без 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 НЕ оптималний
- Занадто багато залежностей (10+) — ознака того, що клас робить занадто багато (порушення SRP). Розбийте на менші класи.
- Циклічні залежності — Spring не зможе створити біни. Використовуйте setter injection або @Lazy.
- Успадкування з обов’язковими залежностями — дочірній клас повинен передати залежності батька через
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
- Constructor — завжди за замовчуванням
- @RequiredArgsConstructor — Lombok для зручності
- @Lazy — не лише для циклічних залежностей. Корисний також для відкладеного завантаження важких бінів (економія пам’яті і часу старту).
- Field Injection — не рекомендований для production коду. У тестах допустимий (тестові класи часто не тестують на DI-патерни).
- > 5 параметрів → порушення SRP
- Конструктор = лише присвоєння полів
Резюме для 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]]