Что такое внедрение зависимостей (Dependency Injection) в Spring?

Ответ

Внедрение зависимостей (Dependency Injection, DI) — это паттерн проектирования, при котором объект получает свои зависимости извне, а не создаёт их самостоятельно. В Spring это реализовано через IoC-контейнер, который управляет жизненным циклом бинов и их зависимостями.

Зачем это нужно:

  • Слабая связанность: Классы не зависят от конкретных реализаций, а от абстракций (интерфейсов).
  • Тестируемость: Зависимости легко подменить моками в unit-тестах.
  • Гибкость: Конфигурацию зависимостей можно менять без изменения кода (например, через конфигурационные файлы или аннотации).

Основные способы внедрения в Spring:

  1. Через конструктор (рекомендуется):

    @Service
    public class OrderService {
        private final PaymentProcessor paymentProcessor;
    
        @Autowired // Начиная с Spring 4.3, для одного конструктора необязательна
        public OrderService(PaymentProcessor paymentProcessor) {
            this.paymentProcessor = paymentProcessor;
        }
    }
  2. Через поле (field injection):
    @Service
    public class OrderService {
        @Autowired
        private PaymentProcessor paymentProcessor;
    }
  3. Через сеттер (setter injection):

    @Service
    public class OrderService {
        private PaymentProcessor paymentProcessor;
    
        @Autowired
        public void setPaymentProcessor(PaymentProcessor paymentProcessor) {
            this.paymentProcessor = paymentProcessor;
        }
    }

Ключевые аннотации: @Autowired, @Qualifier, @Primary, @Resource, @Inject. Контейнер Spring ищет подходящий бин и автоматически связывает его (wires) с целевым компонентом.

Ответ 18+ 🔞

А, слушай, смотри, сейчас объясню про эту всю хуйню с внедрением зависимостей в Spring, чтобы ты не сидел как Герасим, немой, и не мычал "Му-му" на каждый чих.

Короче, представь, ты пишешь какой-нибудь OrderService. И ему, этому сервису, нужен какой-нибудь PaymentProcessor, чтобы деньги брать. Так вот, самый тупой и прямолинейный способ — это внутри сервиса самому, своими руками, создать этот процессор: new SuperPaymentProcessor(). И вроде бы всё работает, да?

А теперь представь, что завтра тебе говорят: "Вася, а давай вместо супер-процессора поставим мега-процессор, он с комиссией 0.5%". И ты, блядь, начинаешь ползать по всему коду, искать все эти new SuperPaymentProcessor() и менять их на new MegaPaymentProcessor(). Это пиздец какой-то, а не жизнь. Терпения ноль, ебать.

Вот чтобы такого не было, умные дядьки придумали Dependency Injection (DI). Суть проста, как три рубля: ты не создаёшь зависимости сам, а говоришь: "Эй, Spring, мне нужен PaymentProcessor!". А он тебе, хитрая жопа, подсовывает готовый, настроенный бин. И если завтра надо поменять реализацию, ты меняешь её в одном месте — в конфигурации контейнера. И всё, блядь, автоматически подтянется. Ёперный театр, а не удобство.

Зачем это, спросишь? Да похуй, спросишь — не спросишь, я всё равно расскажу:

  • Слабая связанность: Твой сервис теперь не привязан намертво к конкретному классу. Он просто знает, что ему дадут что-то, что умеет обрабатывать платежи (PaymentProcessor — интерфейс). А что именно — SuperPayment или MegaPayment — ему, в общем-то, похуй. Главное, чтобы контракт соблюдал.
  • Тестируемость: Это вообще песня. Хочешь протестировать сервис? Подсовываешь ему в тесте не реальный процессор, который лезет в банк, а какую-нибудь заглушку-мок, которая всегда говорит "ОК". И не надо ебаться с сетями, SSL и прочей хуйней. Чистый юнит-тест.
  • Гибкость: Конфигурация отдельно, код отдельно. Меняешь аннотацию или проперти-файл — и получаешь другое поведение. Красота, блядь.

А теперь, как этим пользоваться. Способов, блядь, целый овердохуища, но основных три:

  1. Через конструктор (это сейчас все рекомендуют, и правильно делают): Ты просто объявляешь зависимость как final поле в классе и требуешь её в конструкторе. Spring видит: "Ага, у класса один конструктор, и там нужен PaymentProcessor. Так, у меня такой бин есть, хуяк — и подсовывает". Чисто, надёжно, все зависимости видны как на ладони.

    @Service
    public class OrderService {
        private final PaymentProcessor paymentProcessor; // final, блядь! Менять нельзя!
    
        // @Autowired тут можно не писать, Spring 4.3+ и так сообразит
        public OrderService(PaymentProcessor paymentProcessor) {
            this.paymentProcessor = paymentProcessor; // Всё, приехали, зависимость внедрена
        }
    }
  2. Через поле (field injection). Самый ленивый и опасный способ: Просто навешиваешь @Autowired над полем. Spring через рефлексию (это такая магия, блядь) засовывает туда бин. Минусы? Да пиздец какие: поле нельзя сделать final, скрытая зависимость, хуже тестируется (нужно или Spring-контекст поднимать, или опять же рефлексией пихать).

    @Service
    public class OrderService {
        @Autowired
        private PaymentProcessor paymentProcessor; // Смотрите, какая хитрая жопа — поле приватное, а Spring туда залезет!
    }
  3. Через сеттер (setter injection): Что-то среднее. Не так плохо, как field injection, но и не так круто, как constructor. Удобно, если зависимость опциональная.

    @Service
    public class OrderService {
        private PaymentProcessor paymentProcessor;
    
        @Autowired
        public void setPaymentProcessor(PaymentProcessor paymentProcessor) {
            this.paymentProcessor = paymentProcessor; // Установил и забыл
        }
    }

Ну и напоследок про аннотации, а то ты опять как Герасим мычать начнёшь:

  • @Autowired — основная, "дай сюда бин, какой найдёшь".
  • @Qualifier — если бинов одного типа несколько, уточняешь, какой именно по имени.
  • @Primary — помечаешь бин как основной по умолчанию для своего типа.
  • @Resource / @Inject — аналоги @Autowired из других спецификаций (JSR-250, JSR-330). Почти то же самое, но со своими, блядь, нюансами.

Вот и вся магия. Контейнер Spring — он как тот самый немой Герасим: молчит, блядь, не говорит ничего, но работу делает. Найдёт все зависимости, свяжет их и выдаст тебе готовый, работающий объект. Главное — не проебиться с конфигурацией, а то будет не "Му-му", а полный пиздец.