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

В чому різниця між constructor, setter та field injection?

4. Field — НЕ використовуйте 5. > 5 залежностей → розбийте клас 6. @Lazy — костиль для циклів

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

🟢 Junior Level

Constructor — залежність приходить через конструктор. Setter — залежність приходить через метод. Field — залежність приходить прямо в поле.

// Constructor
public class Service {
    private final Repo repo;
    public Service(Repo repo) { this.repo = repo; }
}

// Setter
public class Service {
    private Repo repo;
    @Autowired public void setRepo(Repo repo) { this.repo = repo; }
}

// Field
public class Service {
    @Autowired private Repo repo;  // Прихована залежність!
}

Рекомендація: за замовчуванням використовуйте Constructor Injection. Setter — для опціональних залежностей (@Autowired(required = false)). Field Injection — лише в тестах.


🟡 Middle Level

Constructor Injection (Золотий стандарт)

// ✅ Переваги:
// 1. final поля → імутабельність
// 2. Об'єкт повністю зібраний → немає NPE
// 3. Тести без Spring: new Service(mockRepo)
// 4. Цикли видно одразу → помилка на старті

Field Injection (Антипатерн)

// ❌ Проблеми:
// 1. Приховані залежності → не видно, що потрібно класу
// 2. Не можна final → можна втратити залежність
// 3. Тести вимагають Spring контекст
// 4. Цикли маскуються → NPE в рантаймі

Setter Injection (Рідко)

// ⚠️ Коли використовувати:
// 1. Опціональні залежності
// 2. Гаряча заміна в рантаймі (рідко!)

@Autowired(required = false)  // Опціонально
public void setRepo(Repo repo) { this.repo = repo; }

Порядок ініціалізації

1. Конструктор → Constructor Injection
2. Створення об'єкта
3. Поля → Field Injection
4. Сетери → Setter Injection
5. @PostConstruct

→ В конструкторі Field і Setter залежності ще NULL!

Коли Setter injection виправданий

  1. Опціональні залежності: @Autowired(required = false) — якщо залежність не надана, бін все одно створиться
  2. Reconfiguration: потрібно поміняти залежність після створення (рідко, зазвичай це антипатерн)
  3. Spring Boot ConfigurationProperties: Spring Boot сам використовує setter injection для binding конфігурації

🔴 Senior Level

AutowiredAnnotationBeanPostProcessor

Spring використовує BPP для DI:
  1. Сканує поля з @Autowired
  2. Сканує конструктори
  3. Сканує сетери
  4. Впроваджує залежності

> **Spring 4.x+:** єдиний конструктор не потребує `@Autowired`. **Spring 6+:** рекомендується constructor injection без анотацій.

→ Reflection API → overhead
→ Constructor Injection трохи швидший (Spring сканує один конструктор замість усіх полів класу). На практиці різниця вимірюється мілісекундами під час старту.

Циклічні залежності

Constructor Injection:
  → A потрібен B, B потрібен A
  → Spring не може створити жоден → помилка одразу

Field Injection:
  → Spring створить A і B через default конструктор
  → Потім заповнить поля
  → Маскує архітектурну проблему!

Конфлікт з CGLIB Proxy

Spring створює проксі через підклас → викликає конструктор
→ Якщо конструктор важкий → виконається двічі!
→ Конструктор повинен бути простим: лише присвоєння полів
→ Вся логіка → в @PostConstruct

Lombok @RequiredArgsConstructor

@Service
@RequiredArgsConstructor
public class Service {
    private final Repo1 repo1;
    private final Repo2 repo2;
    // Lombok сам створить конструктор!
}

Production Experience

Реальний сценарій: 10 залежностей у конструкторі

  • Клас робить занадто багато → порушення SRP
  • Рішення: розбили на 3 сервіси
  • Результат: читабельність + тестованість

Best Practices

  1. Constructor — за замовчуванням (99%)
  2. @RequiredArgsConstructor — Lombok для бойлерплейту
  3. Setter — лише опціональні залежності
  4. Field — НЕ використовуйте
  5. > 5 залежностей → розбийте клас
  6. @Lazy — костиль для циклів

Резюме для Senior

  • Constructor = імутабельність, fail-fast, тести
  • Field = приховані залежності, складно тестувати
  • Setter = опціональні залежності
  • Цикли → Constructor ламається одразу
  • CGLIB → конструктор викликається двічі (об’єкт + проксі)
  • Lombok → @RequiredArgsConstructor прибирає бойлерплейт

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

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

  • Constructor injection — залежність через конструктор, забезпечує final поля та імутабельність
  • Setter injection — через метод, підходить для опціональних залежностей (@Autowired(required = false))
  • Field injection — через поле з @Autowired, антипатерн: приховані залежності, неможливо зробити final
  • Порядок ініціалізації: конструктор → поля (field injection) → сетери → @PostConstruct
  • Constructor injection fail-fast: помилка на старті за відсутності залежності
  • Циклічні залежності через constructor ламають старт, через field — маскують проблему
  • Spring 6+ рекомендує constructor injection без анотацій (якщо конструктор один)

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

  • Чому field injection — антипатерн? Приховані залежності, не можна final, NPE в рантаймі, складно тестувати без Spring.
  • Коли setter injection виправданий? Опціональні залежності, Spring Boot ConfigurationProperties binding, гаряча заміна (рідко).
  • Чому конструктор CGLIB-проксі викликається двічі? Проксі створюється через підклас — спочатку конструктор реального об’єкта, потім конструктор проксі.
  • Що робить AutowiredAnnotationBeanPostProcessor? Сканує @Autowired на полях, конструкторах і сетерах, впроваджує залежності через Reflection.

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

  • «Field Injection — найзручніший, мало коду» (зручність ≠ якість, це технічний борг)
  • «Setter injection підходить для обов’язкових залежностей» (можна забути викликати, об’єкт у неповному стані)
  • «Циклічні залежності — не проблема, Spring впорається» (Spring масковує, але баги в рантаймі)
  • «Lombok повністю вирішує проблему бойлерплейту конструктора» (вирішує, але не прибирає проблему >5 залежностей)

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

  • [[1. Що таке Dependency Injection]]
  • [[3. Який тип ін’єкції рекомендується і чому]]
  • [[4. Що таке Bean в Spring]]
  • [[6. Що таке Bean Lifecycle]]
  • [[7. Які етапи є у Bean lifecycle]]