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

Що таке Dependency Injection?

BeanDefinition — це «рецепт», за яким Spring створює бін: повне ім'я класу, scope, залежності, lazy/eager тощо.

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

🟢 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 НЕ потрібен

  1. Тривіальні скрипти — один клас, одна залежність, немає тестів — DI додасть складності без вигоди
  2. Value Objects / DTO — прості об’єкти з даними, у них немає залежностей
  3. 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

  1. Constructor Injection — завжди за замовчуванням
  2. @Lazy — лише для циклів (краще рефакторинг)
  3. ObjectProvider — для Prototype в Singleton
  4. Field Injection — НЕ використовуйте
  5. > 5 залежностей → порушення SRP, розбийте клас
  6. 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]]