Как реализуется Dependency Injection (DI) в Spring Boot?

Ответ

Spring Boot реализует Dependency Injection (DI) через механизм Inversion of Control (IoC) контейнера, который автоматически управляет жизненным циклом объектов (бинов) и их зависимостями.

Основные механизмы:

  1. Объявление бинов:

    • Стереотипные аннотации: @Component, @Service, @Repository, @Controller. Spring сканирует класспат и регистрирует помеченные классы как бины.
    • Явное объявление: В классах, помеченных @Configuration, методы с @Bean возвращают экземпляр бина.
  2. Внедрение зависимостей:

    • Через конструктор (рекомендуется): Наиболее предпочтительный способ, обеспечивает неизменяемость и обязательность зависимости.

      @Service
      public class UserService {
          private final UserRepository userRepo;
      
          @Autowired // Необязательно с Spring 4.3+
          public UserService(UserRepository userRepo) {
              this.userRepo = userRepo;
          }
      }
    • Через поле: Используется @Autowired над полем. Менее тестируемо и скрывает обязательные зависимости.
    • Через сеттер: Аналогично полю, но с аннотацией над setter-методом.

Как это работает в Spring Boot:

  • Аннотация @SpringBootApplication включает автоматическое сканирование компонентов и автоконфигурацию.
  • Контейнер IoC создает граф зависимостей, разрешая их на этапе запуска приложения.
  • Для устранения неоднозначности (несколько бинов одного типа) используются @Qualifier или @Primary.

Ответ 18+ 🔞

А, слушай, вот эта ваша магия Spring Boot, где всё само подключается! Ну, типа, не совсем само, конечно, но почти. Смотри, как это работает, если на пальцах.

Представь себе бар. Ты — бармен (это твоё приложение). Тебе для коктейля нужны: водка, тоник, лёд. Это твои зависимости (dependencies). Раньше ты сам бегал на склад, искал эти бутылки, ковырялся в морозилке — это был ручной контроль. А теперь приходит Spring Boot, такой зазывала, и говорит: «Расслабься, чувак, я тебе всё сам принесу и даже в шейкер положу. Ты только скажи, что тебе надо». Это и есть Inversion of Control (IoC) — контроль перевернулся, теперь не ты за всем бегаешь, а за тобой.

А сам процесс, когда этот зазывала тебе в руки суёт нужную бутылку, — это и есть Dependency Injection (DI), внедрение зависимости. Не ты её берёшь, а тебе её внедряют прямо в руки, в рот меня чих-пых!

Как он узнаёт, что тебе принести? Есть два основных способа:

  1. Кричишь громко (Стереотипные аннотации). Ты просто вешаешь на складскую дверь табличку: «Здесь Водка» (@Component), «Здесь Тоник» (@Service), «Здесь Лёд» (@Repository). Spring Boot, как сканер штрих-кодов, бегает по всему твоему проекту (класс-пасу), видит эти таблички и говорит: «Ага, вот это бин! Беру в список». Бин — это просто объект, которым он будет управлять.

  2. Даёшь письменную инструкцию (Явное объявление). Бывает, компонент такой хитрожопый, что табличкой не обойдёшься. Например, тебе нужна не просто водка, а водка, настоянная на перце. Тогда ты идешь в отдельный конфигурационный кабинет (класс с @Configuration) и пишешь метод с рецептом, помечая его @Bean. Spring Boot выполнит этот метод, получит твою перцовку и тоже запишет её как бин.

А как он эти бины тебе впихивает? Тоже есть варианты, но есть один, который все умные дяди рекомендуют:

  • Через конструктор (Идеально!). Ты заранее объявляешь: «Слушай, Spring, я бармен UserService, но я нихрена не работаю без UserRepository! Не принесёшь — я даже создаваться не буду». И прописываешь это в конструкторе.

    @Service
    public class UserService {
        private final UserRepository userRepo; // Финал! Менять нельзя!
    
        // Spring видит конструктор и понимает: "О, чуваку нужен репозиторий. Ща найдём и сунем."
        public UserService(UserRepository userRepo) {
            this.userRepo = userRepo; // Всё, зависимость внедрена. Красиво и надёжно.
        }
    }

    Раньше надо было ставить над конструктором @Autowired, но теперь Spring такой догадливый, что если конструктор один, то он всё понимает сам. Умный, блядь, как мартышлюшка!

  • Через поле (Быстро, но криво). Ты просто объявляешь поле и ставишь над ним @Autowired. Spring найдёт бин и впендюрит его туда напрямую, через отражение. Минус — хрен протестируешь, и не видно, что зависимость обязательная. Выглядит как чёрная магия, а магия, она, случается, и подводит.

  • Через сеттер (Ну такое...). Почти как поле, но аннотацию вешаешь на метод set. Чуть лучше, но всё равно не айс.

И как же вся эта ёбаная кухня заводится в Spring Boot? Всё начинается с аннотации @SpringBootApplication. Это такая красная кнопка «ВКЛ». Она включает:

  1. Сканирование компонентов: Spring начинает рыскать по пакетам и искать твои таблички (@Component, @Service и т.д.).
  2. Автоконфигурацию: Spring Boot сам догадывается, что у тебя, например, есть библиотека для работы с БД, и подсовывает тебе готовые настройки подключения. Не надо писать хуеву тучу конфигов!

Дальше контейнер IoC строит из всех найденных бинов граф: кто от кого зависит. И на этапе запуска приложения он этот граф собирает, как конструктор «Лего». Если всё нашёл — приложение стартует. Если нет — получаешь ошибку, что UserService не может найти бин типа UserRepository. И тут начинается дебаг, волнение ебать, терпения ноль ебать!

А если бинов одного типа несколько? Представь, у тебя на складе три сорта водки: «Столичная», «Абсолют» и какая-то местная «Беленькая». Spring увидит три бина типа Vodka и обосрётся от неопределённости: «Какую тебе, мудак, нести?!». Тут на помощь приходят:

  • @Qualifier("stolichnaya") — ты прямо указываешь, какую именно.
  • @Primary — ты на одну из бутылок вешаешь бирку «По умолчанию бери эту». И если явно не указано, Spring будет тыкать её.

Вот, короче, и вся магия. Не самолётостроение, но когда понимаешь, как оно внутри работает, перестаёшь бояться этих @Autowired и начинаешь их уважать. Ну, почти.