В чём разница между 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. Какой тип injection рекомендуется использовать и почему]]
- [[4. Что такое Bean в Spring]]
- [[6. Что такое Bean Lifecycle]]
- [[7. Какие этапы жизненного цикла Bean]]