Получить доступ

IoC и Dependency Injection

Принцип инверсии управления, DI как его форма, три способа внедрения и почему конструктор, разрешение циклических зависимостей.

Inversion of Control

Inversion of Control (инверсия управления) — принцип: контроль над созданием, конфигурацией и жизненным циклом объектов передаётся внешнему контейнеру, а не пишется руками в коде. Это «Hollywood principle»: не вы зовёте фреймворк, а он зовёт ваш код.

В Spring роль контейнера играет ApplicationContext: он создаёт объекты-бины, связывает их зависимости и управляет их жизнью. Ваш код перестаёт делать new для своих зависимостей.

Dependency Injection

DI — конкретная форма IoC: зависимости объекта передаются ему извне (контейнером), а не создаются им самим внутри через new. Объект лишь объявляет, что ему нужно, а кто и как это предоставит — забота контейнера.

Важно не путать понятия: IoC — широкий принцип (управление отдано наружу), DI — один из способов его реализовать (через внедрение зависимостей). На собеседовании это частый уточняющий вопрос.

Три способа внедрения

Constructor injection — зависимости приходят параметрами конструктора (рекомендуемый). Setter injection — через сеттеры (для опциональных или переустанавливаемых зависимостей). Field injection — прямо в поле через @Autowired (не рекомендуется).

С единственным конструктором аннотация @Autowired не нужна — Spring использует его автоматически.

@Service
class OrderService {
    private final PaymentGateway gateway; // final — гарантированно установлен
    OrderService(PaymentGateway gateway) { // один конструктор — @Autowired не нужен
        this.gateway = gateway;
    }
}

Почему конструктор

Конструкторная инъекция объективно лучше, а не просто «стиль». Поля можно сделать final (иммутабельность и безопасная публикация). Объект всегда создаётся полностью собранным — не бывает промежуточного состояния с null-зависимостями. Обязательные зависимости становятся явными: класс невозможно создать без них (fail-fast уже на старте контекста, а не NPE в рантайме).

И главное для качества кода — тестируемость: класс с конструкторной инъекцией тестируется обычным new с моками, без поднятия Spring-контекста. Field injection, наоборот, прячет зависимости (по сигнатуре не видно, что нужно классу) и требует рефлексии/контейнера в тестах.

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

Если A зависит от B, а B — от A через конструкторы, контейнер не может их собрать (для создания A нужен готовый B и наоборот) и бросает BeanCurrentlyInCreationException на старте.

Цикл «лечат» setter/field-инъекцией или @Lazy/ObjectProvider, которые откладывают получение зависимости. Но это симптом: чаще всего циклическая зависимость — признак плохого дизайна, и правильный путь — рефакторинг (выделить третий компонент с общей логикой), а не обход. С Spring Boot 2.6+ циклы по умолчанию запрещены, что подталкивает именно к рефакторингу.

Как отвечать на собеседовании

Чётко разделите IoC (принцип: управление отдано контейнеру) и DI (форма IoC: внедрение зависимостей). Перечислите три вида инъекции и аргументируйте конструктор по существу (final, полностью собранный объект, явные обязательные зависимости, fail-fast, тестируемость без контекста). Объясните, почему конструкторный цикл неразрешим и что это повод к рефакторингу, а не к обходу.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем