Какой тип injection рекомендуется использовать и почему?
4. Field Injection — не рекомендован для production кода. В тестах допустим (тестовые классы часто не тестируют на DI-паттерны). 5. > 5 параметров → нарушение SRP 6. Конструктор...
🟢 Junior Level
Рекомендуется: Constructor Injection!
// ✅ Правильно
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo;
}
// ❌ Неправильно
@Service
public class OrderService {
@Autowired private OrderRepository repo; // Field injection
}
Почему:
- Можно сделать поле
final - Приложение упадёт сразу, если зависимости нет
- Легко тестировать без Spring
🟡 Middle Level
Преимущества Constructor Injection
1. Иммутабельность:
private final Repo repo; // Нельзя изменить после создания
2. Fail-fast:
Constructor: ошибка при старте, если зависимости нет
Field: объект создастся, NPE при первом вызове метода
3. Тестирование:
// Constructor: тест без Spring
OrderService service = new OrderService(mockRepo);
// Field: нужен @SpringBootTest или рефлексия
4. Циклы:
Constructor → ошибка сразу → чините архитектуру
Field → работает → баги в рантайме
Когда Spring выбирает конструктор
// Один конструктор → Spring использует его автоматически
public Service(Repo repo) { } // @Autowired не нужен!
// Несколько конструкторов:
// 1. Ищет @Autowired
// 2. Если нет → конструктор по умолчанию
// 3. Если нет → ошибка
Проблема циклов
// ❌ A → B → A через конструкторы
// Spring не может создать ни один → ошибка
// ✅ Рефакторинг: выделить общий сервис C
// A → C, B → C (нет цикла!)
// ⚠️ Костыль: @Lazy
public Service(@Lazy Dependency dep) { }
// → Spring внедрит прокси, реальная зависимость при первом вызове
Когда Constructor Injection НЕ оптимален
- Слишком много зависимостей (10+) — признак, что класс делает слишком много (нарушение SRP). Разбейте на меньшие классы.
- Циклические зависимости — Spring не сможет создать бины. Используйте setter injection или @Lazy.
- Наследование с обязательными зависимостями — дочерний класс должен передать зависимости родителя через
super().
🔴 Senior Level
Конфликт с CGLIB Proxy
Spring AOP создаёт прокси через подкласс:
1. new Service(...) → реальный объект
2. CGLIB подкласс → прокси
→ Конструктор выполняется ДВАЖДЫ!
→ Тяжёлая логика в конструкторе = проблема
Решение:
→ Конструктор = только присвоение полей
→ Инициализация → в @PostConstruct
Under the hood: как Spring внедряет
AutowiredAnnotationBeanPostProcessor:
1. Нашёл конструктор с @Autowired (или единственный)
2. Разрешил зависимости через BeanFactory
3. Вызвал конструктор
→ Reflection → overhead
→ Constructor Injection немного быстрее при старте (один конструктор vs все поля). На практике разница минимальна, главная причина — безопасность и тестируемость.
Production Experience
Реальный сценарий: Field Injection скрыл 20 циклов
- Приложение стартовало, но падало рандомно
- 4 часа дебага → нашли циклические зависимости
- Перешли на Constructor Injection → ошибка на старте → починили за 10 минут
Best Practices
- Constructor — всегда по умолчанию
- @RequiredArgsConstructor — Lombok для удобства
- @Lazy — не только для циклических зависимостей. Полезен также для отложенной загрузки тяжёлых бинов (экономия памяти и времени старта).
- Field Injection — не рекомендован для production кода. В тестах допустим (тестовые классы часто не тестируют на DI-паттерны).
- > 5 параметров → нарушение SRP
- Конструктор = только присвоение полей
Резюме для Senior
- Constructor = иммутабельность, fail-fast, тесты
- Field = технический долг
- Циклы → Constructor ломается сразу (хорошо!)
- CGLIB → конструктор вызывается дважды
- @Lazy → прокси-заглушка, реальная при вызове
- SRP → > 5 зависимостей = разбейте класс
🎯 Шпаргалка для интервью
Обязательно знать:
- Рекомендуемый тип — Constructor Injection: иммутабельность (
final), fail-fast, удобное тестирование без Spring - Constructor Injection делает зависимости явными — видно, что нужно классу
- При отсутствии зависимости приложение упадёт на старте, а не в рантайме (NPE)
- Тестирование без Spring:
new Service(mockRepo)— быстро и просто - Циклические зависимости через constructor ломают старт — это хорошо, сразу видна проблема
- Конструктор должен содержать только присвоение полей, вся логика — в
@PostConstruct @Lazyрешает циклы через прокси-заглушку, но это костыль — лучше рефакторинг-
5 параметров конструктора → нарушение SRP, разбейте класс
Частые уточняющие вопросы:
- Почему @Lazy — костыль? Он маскирует циклические зависимости, не устраняя архитектурную проблему.
- Почему конструктор выполняется дважды с CGLIB? Spring создаёт реальный объект + подкласс-прокси, оба вызывают конструктор.
- Как Spring выбирает конструктор? Если конструктор один — автоматически. Если несколько — ищет
@Autowired. - Когда Constructor Injection НЕ оптимален? Слишком много зависимостей (10+), циклы, наследование с обязательными зависимостями.
Красные флаги (НЕ говорить):
- «Field Injection допустим в production коде» (не рекомендован, только в тестах)
- «@Lazy — правильное решение для циклов» (костыль, лучше рефакторинг)
- «Чем больше зависимостей, тем лучше — класс мощнее» (нарушение SRP, разбейте на части)
- «Constructor Injection медленнее из-за Reflection» (разница в миллисекундах, главное — безопасность)
Связанные темы:
- [[1. Что такое Dependency Injection]]
- [[2. В чём разница между constructor, setter и field injection]]
- [[6. Что такое Bean Lifecycle]]
- [[7. Какие этапы жизненного цикла Bean]]
- [[9. Что делают методы с аннотацией @PostConstruct и @PreDestroy]]