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

Что такое Dependency Injection?

BeanDefinition — это «рецепт», по которому Spring создаёт бин: полное имя класса, scope, зависимости, lazy/eager и т.д.

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

🟢 Junior Level

Dependency Injection (DI) — это паттерн, при котором объекты получают свои зависимости извне, а не создают их сами через new.

Зачем: без DI класс жёстко связан с зависимостями. Его невозможно протестировать изолированно (моки не подставить), а замена реализации требует правки кода. DI решает это — зависимости поставляются извне.

Простая аналогия: Вместо того чтобы самому строить машину, вам её привозят на заказ.

Без DI:

// ❌ Класс сам создаёт зависимости
public class OrderService {
    private OrderRepository repo = new OrderRepository();
}

С DI:

// ✅ Зависимость приходит извне
public class OrderService {
    private final OrderRepository repo;
    
    public OrderService(OrderRepository repo) {
        this.repo = repo;  // Spring сам передаст нужный репозиторий
    }
}

Зачем:

  • Легко тестировать (подменить репозиторий на мок)
  • Классы не зависят друг от друга напрямую
  • Spring управляет всеми объектами сам

Когда DI НЕ нужен

  1. Тривиальные скрипты — один класс, одна зависимость, нет тестов — DI добавит сложности без выгоды
  2. Value Objects / DTO — простые объекты с данными, у них нет зависимостей
  3. Performance-critical системы с микросекундным latency — DI-контейнер добавляет overhead при старте (обычно пренебрежимо)

🟡 Middle Level

Жизненный цикл DI в Spring

1. Registration: Сканирование (@Component, @Bean) → BeanDefinition
2. Instantiation: Создание объекта (рефлексия)
3. Population: Внедрение зависимостей (@Autowired)
4. Initialization: @PostConstruct, Proxy creation (@Transactional)
5. Ready: Бин готов к работе

BeanDefinition — это «рецепт», по которому Spring создаёт бин: полное имя класса, scope, зависимости, lazy/eager и т.д.

Proxy creation — Spring создаёт объект-заменитель (прокси), который перехватывает вызовы методов и добавляет поведение (транзакции, безопасность). Подробности — в файле [[13. Что такое прокси в Spring]].

Типы инъекции

Тип Пример Плюсы Минусы
Constructor public Service(Repo r) {} Immutable, тесты, fail-fast Много параметров
Setter @Autowired void setRepo(Repo r) {} Опциональные зависимости Можно забыть вызвать
Field @Autowired Repo repo; Мало кода ❌ Скрытые зависимости, сложно тестировать

Циклические зависимости

// A зависит от B, B зависит от A
// ❌ Constructor Injection → ошибка на старте
// ✅ @Lazy → Spring создаст прокси, реальная зависимость подставится позже

Prototype в Singleton

// ❌ Prototype создастся ОДИН раз при внедрении в Singleton
@Autowired PrototypeBean proto;  // Застрял один экземпляр

// ✅ ObjectProvider — каждый раз новый
@Autowired ObjectProvider<PrototypeBean> provider;
PrototypeBean bean = provider.getObject();  // Новый!

🔴 Senior Level

BeanDefinition: мета-информация

BeanDefinition хранит:
  → Имя класса
  → Scope (Singleton, Prototype)
  → Constructor arguments
  → Autowire mode
  → Init/Destroy methods
  
→ Можно менять через BeanFactoryPostProcessor ДО создания бинов

Proxy и DI

DI происходит ДО создания прокси!
  1. Spring создаёт объект
  2. Внедряет зависимости
  3. BeanPostProcessor.postProcessAfterInitialization → создаёт Proxy
  
→ Поэтому @Transactional не работает при self-invocation!
→ Вызов this.method() идёт мимо прокси

Constructor Injection и Circular Dependencies

Constructor Injection требует зависимости СРАЗУ.
  → Spring не может создать A без B
  → Spring не может создать B без A
  → BeanCurrentlyInCreationException
  
Field Injection:
  → Spring создаст A и B через конструктор по умолчанию
  → Потом заполнит поля
  → Скрытый баг: NPE в @PostConstruct

Production Experience

Реальный сценарий: Field Injection скрыл проблему

  • 50 сервисов, циклические зависимости через Field Injection
  • Приложение стартовало, но падало при первом запросе
  • Решение: перешли на Constructor Injection
  • Результат: ошибка на старте → быстро починили

Best Practices

  1. Constructor Injection — всегда по умолчанию
  2. @Lazy — только для циклов (лучше рефакторинг)
  3. ObjectProvider — для Prototype в Singleton
  4. Field Injection — НЕ используйте
  5. > 5 зависимостей → нарушение SRP, разбейте класс
  6. DI ≠ Service Locator → не тяните контекст в код

Резюме для Senior

  • DI = реализация Inversion of Control. IoC — общий принцип («фреймворк вызывает ваш код, а не наоборот»). DI — конкретная реализация IoC, когда зависимости поставляются извне. Не все IoC — это DI, но весь DI — это IoC.
  • Constructor — иммутабельность, тесты, fail-fast
  • BeanFactoryPostProcessor → изменение мета-данных до создания
  • Proxy создаётся ПОСЛЕ DI → self-invocation не работает
  • Circular deps → Constructor ломается сразу, Field маскирует
  • Prototype в Singleton → ObjectProvider или @Lookup
  • Field Injection — технический долг

🎯 Шпаргалка для интервью

Обязательно знать:

  • DI — паттерн, при котором объекты получают зависимости извне, а не создают через new
  • DI реализует Inversion of Control (IoC): фреймворк вызывает ваш код, а не наоборот
  • Три типа injection: constructor, setter, field
  • DI облегчает тестирование (моки), снижает связанность, упрощает замену реализаций
  • Spring управляет жизненным циклом DI: Registration → Instantiation → Population → Initialization → Ready
  • Циклические зависимости: constructor ломается сразу, field маскирует проблему
  • Prototype в Singleton требует ObjectProvider, иначе будет один экземпляр
  • 5 зависимостей → нарушение SRP, разбейте класс

Частые уточняющие вопросы:

  • Чем DI отличается от IoC? IoC — общий принцип, DI — конкретная реализация IoC.
  • Что такое BeanDefinition? «Рецепт» бина: имя класса, scope, зависимости, init/destroy методы.
  • Почему @Transactional не работает при self-invocation? Вызов this.method() идёт мимо прокси, которая создаётся ПОСЛЕ DI.
  • Как решить циклические зависимости? Рефакторинг (выделить общий сервис), @Lazy как костыль, или setter injection.

Красные флаги (НЕ говорить):

  • «Field Injection — нормальный выбор для production» (это антипаттерн, скрывает зависимости)
  • «DI добавляет слишком много overhead, лучше вручную через new» (overhead ничтожен, выгоды перевешивают)
  • «Циклические зависимости — это нормально, Spring их сам решает» (Spring маскирует архитектурную проблему)
  • «Все объекты должны быть бинами с DI» (DTO, Value Objects, Entity — обычные объекты)

Связанные темы:

  • [[2. В чём разница между constructor, setter и field injection]]
  • [[3. Какой тип injection рекомендуется использовать и почему]]
  • [[4. Что такое Bean в Spring]]
  • [[6. Что такое Bean Lifecycle]]
  • [[8. Что такое BeanPostProcessor]]