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