Урок по треку Spring и Spring Boot 7 мин

IoC и внедрение зависимостей в Spring

Контейнер, ApplicationContext, способы внедрения, @Autowired, @Qualifier, @Primary и циклические зависимости.

Первый вопрос почти любого собеса по Spring: что такое IoC и DI и зачем они нужны. Дальше идут способы внедрения, разрешение неоднозначностей и циклические зависимости.

IoC и DI

Inversion of Control: объекты не создают свои зависимости сами, их создаёт и связывает контейнер. Dependency Injection это способ реализовать IoC: зависимости передаются объекту снаружи (через конструктор, сеттер или поле).

// без DI: класс сам решает, какую реализацию взять
class OrderService {
    private final PaymentClient client = new HttpPaymentClient("https://pay");
}

// с DI: класс объявляет, что ему нужно
@Service
class OrderService {
    private final PaymentClient client;
    OrderService(PaymentClient client) { this.client = client; }
}

Выигрыш: слабая связанность, подмена реализации без правки кода, простые юнит-тесты (передал мок в конструктор).

Контейнер

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

Откуда контейнер узнаёт о бинах:

  • @Component и производные (@Service, @Repository, @Controller) плюс сканирование пакетов (@ComponentScan, его включает @SpringBootApplication);
  • методы @Bean в классах @Configuration, когда класс не ваш или создание нетривиально;
  • XML (в новых проектах почти не встречается).

Способы внедрения

СпособПлюсыМинусы
Конструкторобязательные зависимости, final-поля, легко тестироватьдлинный конструктор сразу показывает, что класс делает слишком много (это скорее плюс)
Сеттернеобязательные зависимости, перенастройкаобъект может оказаться не до конца настроен
Поле (@Autowired на поле)коротконельзя final, в тестах нужен Spring или рефлексия, скрывает зависимости

Рекомендация Spring: внедрение через конструктор. Если конструктор единственный, @Autowired на нём можно не писать (с Spring 4.3). С Lombok часто используют @RequiredArgsConstructor.

Когда кандидатов несколько

interface Notifier {}
@Component class EmailNotifier implements Notifier {}
@Component class SmsNotifier implements Notifier {}

@Service
class AlertService {
    AlertService(Notifier notifier) {}   // NoUniqueBeanDefinitionException
}

Варианты решения:

  • @Primary на одной реализации: она выбирается по умолчанию;
  • @Qualifier("smsNotifier") в точке внедрения (имеет приоритет над @Primary);
  • имя параметра совпадает с именем бина (запасной вариант, работает при сохранённых именах параметров);
  • List<Notifier> или Map<String, Notifier>: внедряются все реализации, удобно для стратегий.

Optional<T>, ObjectProvider<T> и @Autowired(required = false) для необязательных зависимостей. ObjectProvider ещё и откладывает получение бина до момента вызова.

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

A требует B, B требует A. С внедрением через конструкторы Spring не может создать ни один бин и падает с BeanCurrentlyInCreationException. Начиная с Spring Boot 2.6 циклы запрещены по умолчанию и для полей и сеттеров (spring.main.allow-circular-references=false).

Правильное решение не флаг и не @Lazy, а дизайн: вынести общую логику в третий бин, развернуть зависимость через событие или интерфейс. @Lazy на параметре конструктора подставляет прокси и откладывает создание, это допустимый временный обход.

Частые ошибки

  • Создавать бин через new и удивляться, что внутри не работают @Autowired и @Transactional: объект не управляется контейнером.
  • Внедрение в поля и тесты, которые без Spring не запустить.
  • Лечить цикл флагом allow-circular-references.

Что спросят на собесе

  • Что такое IoC и DI, чем они помогают?
  • Какие способы внедрения есть и почему рекомендуют конструктор?
  • Что будет, если подходящих бинов два? Как работают @Primary и @Qualifier?
  • Чем BeanFactory отличается от ApplicationContext?
  • Как Spring ведёт себя с циклическими зависимостями и как их устранить?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.