Що таке Dependency Injection?
BeanDefinition — це «рецепт», за яким Spring створює бін: повне ім'я класу, scope, залежності, lazy/eager тощо.
🟢 Junior Level
Dependency Injection (DI) — це патерн, при якому об’єкти отримують свої залежності ззовні, а не створюють їх самі через new.
Навіщо: без DI клас жорстко пов’язаний із залежностями. Його неможливо протестувати ізольовано (моки не підставити), а заміна реалізації вимагає правки коду. DI вирішує це — залежності постачаються ззовні.
Проста аналогія: Замість того щоб самому будувати машину, вам її привозять на замовлення.
Без DI:
// ❌ Клас сам створює залежності
public class OrderService {
private OrderRepository repo = new OrderRepository();
}
З DI:
// ✅ Залежність приходить ззовні
public class OrderService {
private final OrderRepository repo;
public OrderService(OrderRepository repo) {
this.repo = repo; // Spring сам передасть потрібний репозиторій
}
}
Навіщо:
- Легко тестувати (підмінити репозиторій на мок)
- Класи не залежать один від одного напряму
- Spring управляє всіма об’єктами сам
Коли DI НЕ потрібен
- Тривіальні скрипти — один клас, одна залежність, немає тестів — DI додасть складності без вигоди
- Value Objects / DTO — прості об’єкти з даними, у них немає залежностей
- Performance-critical системи з мікросекундним latency — DI-контейнер додає overhead під час старту (зазвичай незначно)
🟡 Middle Level
Життєвий цикл DI в Spring
1. Registration: Сканування (@Component, @Bean) → BeanDefinition
2. Instantiation: Створення об'єкта (рефлексія)
3. Population: Впровадження залежностей (@Autowired)
4. Initialization: @PostConstruct, Proxy creation (@Transactional)
5. Ready: Бін готовий до роботи
BeanDefinition — це «рецепт», за яким Spring створює бін: повне ім’я класу, scope, залежності, lazy/eager тощо.
Proxy creation — Spring створює об’єкт-замінник (проксі), який перехоплює виклики методів і додає поведінку (транзакції, безпека). Подробиці — у файлі [[13. Що таке proxy в Spring]].
Типи ін’єкції
| Тип | Приклад | Плюси | Мінусы |
|---|---|---|---|
| Constructor | public Service(Repo r) {} |
Immutable, тести, fail-fast | Багато параметрів |
| Setter | @Autowired void setRepo(Repo r) {} |
Опціональні залежності | Можна забути викликати |
| Field | @Autowired Repo repo; |
Мало коду | ❌ Приховані залежності, складно тестувати |
Циклічні залежності
// A залежить від B, B залежить від A
// ❌ Constructor Injection → помилка на старті
// ✅ @Lazy → Spring створить проксі, реальна залежність підставиться пізніше
Prototype в Singleton
// ❌ Prototype створиться ОДИН раз при впровадженні в Singleton
@Autowired PrototypeBean proto; // Застряв один екземпляр
// ✅ ObjectProvider — щоразу новий
@Autowired ObjectProvider<PrototypeBean> provider;
PrototypeBean bean = provider.getObject(); // Новий!
🔴 Senior Level
BeanDefinition: мета-інформація
BeanDefinition зберігає:
→ Ім'я класу
→ Scope (Singleton, Prototype)
→ Constructor arguments
→ Autowire mode
→ Init/Destroy methods
→ Можна змінювати через BeanFactoryPostProcessor ДО створення бінів
Proxy та DI
DI відбувається ДО створення проксі!
1. Spring створює об'єкт
2. Впроваджує залежності
3. BeanPostProcessor.postProcessAfterInitialization → створює Proxy
→ Тому @Transactional не працює при self-invocation!
→ Виклик this.method() йде повз проксі
Constructor Injection та Circular Dependencies
Constructor Injection вимагає залежності НЕГАЙНО.
→ Spring не може створити A без B
→ Spring не може створити B без A
→ BeanCurrentlyInCreationException
Field Injection:
→ Spring створить A і B через конструктор за замовчуванням
→ Потім заповнить поля
→ Прихований баг: NPE в @PostConstruct
Production Experience
Реальний сценарій: Field Injection приховав проблему
- 50 сервісів, циклічні залежності через Field Injection
- Додаток стартував, але падав при першому запиті
- Рішення: перейшли на Constructor Injection
- Результат: помилка на старті → швидко полагодии
Best Practices
- Constructor Injection — завжди за замовчуванням
- @Lazy — лише для циклів (краще рефакторинг)
- ObjectProvider — для Prototype в Singleton
- Field Injection — НЕ використовуйте
- > 5 залежностей → порушення SRP, розбийте клас
- DI ≠ Service Locator → не тягніть контекст у код
Резюме для Senior
- DI = реалізація Inversion of Control. IoC — загальний принцип («фреймворк викликає ваш код, а не навпаки»). DI — конкретна реалізація IoC, коли залежності постачаються ззовні. Не всі IoC — це DI, але весь DI — це IoC.
- Constructor — імутабельність, тести, fail-fast
- BeanFactoryPostProcessor → зміна мета-даних до створення
- Proxy створюється ПІСЛЯ DI → self-invocation не працює
- Circular deps → Constructor ламається одразу, Field масковує
- Prototype в Singleton → ObjectProvider або @Lookup
- Field Injection — технічний борг
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- DI — патерн, при якому об’єкти отримують залежності ззовні, а не створюють через
new - DI реалізує Inversion of Control (IoC): фреймворк викликає ваш код, а не навпаки
- Три типи injection: constructor, setter, field
- DI полегшує тестування (моки), знижує зв’язаність, спрощує заміну реалізацій
- Spring управляє життєвим циклом DI: Registration → Instantiation → Population → Initialization → Ready
- Циклічні залежності: constructor ламається одразу, field масковує проблему
- Prototype в Singleton вимагає ObjectProvider, інакше буде один екземпляр
-
5 залежностей → порушення SRP, розбийте клас
Часті уточнюючі запитання:
- Чим DI відрізняється від IoC? IoC — загальний принцип, DI — конкретна реалізація IoC.
- Що таке BeanDefinition? «Рецепт» біна: ім’я класу, scope, залежності, init/destroy методи.
- Чому @Transactional не працює при self-invocation? Виклик
this.method()йде повз проксі, яка створюється ПІСЛЯ DI. - Як вирішити циклічні залежності? Рефакторинг (виділити спільний сервіс), @Lazy як костиль, або setter injection.
Червоні прапорці (НЕ говорити):
- «Field Injection — нормальний вибір для production» (це антипатерн, приховує залежності)
- «DI додає занадто багато overhead, краще вручну через
new» (overhead незначний, вигоди переважують) - «Циклічні залежності — це нормально, Spring їх сам вирішує» (Spring масковує архітектурну проблему)
- «Всі об’єкти повинні бути бінами з DI» (DTO, Value Objects, Entity — звичайні об’єкти)
Пов’язані теми:
- [[2. В чому різниця між constructor, setter та field injection]]
- [[3. Який тип ін’єкції рекомендується і чому]]
- [[4. Що таке Bean в Spring]]
- [[6. Що таке Bean Lifecycle]]
- [[8. Що таке BeanPostProcessor]]