Вопрос 3 · Раздел 5

Какой тип injection рекомендуется использовать и почему?

4. Field Injection — не рекомендован для production кода. В тестах допустим (тестовые классы часто не тестируют на DI-паттерны). 5. > 5 параметров → нарушение SRP 6. Конструктор...

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Рекомендуется: Constructor Injection!

// ✅ Правильно
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository repo;
}

// ❌ Неправильно
@Service
public class OrderService {
    @Autowired private OrderRepository repo;  // Field injection
}

Почему:

  1. Можно сделать поле final
  2. Приложение упадёт сразу, если зависимости нет
  3. Легко тестировать без 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 НЕ оптимален

  1. Слишком много зависимостей (10+) — признак, что класс делает слишком много (нарушение SRP). Разбейте на меньшие классы.
  2. Циклические зависимости — Spring не сможет создать бины. Используйте setter injection или @Lazy.
  3. Наследование с обязательными зависимостями — дочерний класс должен передать зависимости родителя через 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

  1. Constructor — всегда по умолчанию
  2. @RequiredArgsConstructor — Lombok для удобства
  3. @Lazy — не только для циклических зависимостей. Полезен также для отложенной загрузки тяжёлых бинов (экономия памяти и времени старта).
  4. Field Injection — не рекомендован для production кода. В тестах допустим (тестовые классы часто не тестируют на DI-паттерны).
  5. > 5 параметров → нарушение SRP
  6. Конструктор = только присвоение полей

Резюме для 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]]