Что такое Dependency Injection?
BeanDefinition — это «рецепт», по которому Spring создаёт бин: полное имя класса, scope, зависимости, lazy/eager и т.д.
🟢 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 НЕ нужен
- Тривиальные скрипты — один класс, одна зависимость, нет тестов — DI добавит сложности без выгоды
- Value Objects / DTO — простые объекты с данными, у них нет зависимостей
- 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
- Constructor Injection — всегда по умолчанию
- @Lazy — только для циклов (лучше рефакторинг)
- ObjectProvider — для Prototype в Singleton
- Field Injection — НЕ используйте
- > 5 зависимостей → нарушение SRP, разбейте класс
- 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]]