В чому різниця між constructor, setter та field injection?
4. Field — НЕ використовуйте 5. > 5 залежностей → розбийте клас 6. @Lazy — костиль для циклів
🟢 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 виправданий
- Опціональні залежності:
@Autowired(required = false)— якщо залежність не надана, бін все одно створиться - Reconfiguration: потрібно поміняти залежність після створення (рідко, зазвичай це антипатерн)
- 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
- Constructor — за замовчуванням (99%)
- @RequiredArgsConstructor — Lombok для бойлерплейту
- Setter — лише опціональні залежності
- Field — НЕ використовуйте
- > 5 залежностей → розбийте клас
- @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]]