Почему Spring стал популярным — автоматизация DI и IoC на практике

Содержание

Переработанная версия

Это обновлённая версия моей старой статьи, изначально опубликованной 14.01.2024. Исправлены ошибки, убраны неточности, удалены лишние примеры, а весь текст полностью отформатирован для лучшей читаемости.

Одной из причин, почему Spring Framework получил массовое распространение в начале 2000-х, стала его способность автоматизировать применение принципов инверсии управления (IoC) и внедрения зависимостей (DI).

Эти принципы существовали и до Spring, но внедрялись вручную, что требовало большого количества кода и дисциплины. Spring же предложил контейнер, который сам создаёт объекты, настраивает их и соединяет между собой, избавляя разработчиков от рутинной работы.

Как было до Spring

В эпоху раннего Java EE (J2EE) DI приходилось делать вручную. Объекты часто создавали свои зависимости сами, что приводило к сильной связанности.

public class OrderService {
    private InventoryService inventoryService = new InventoryServiceImpl();

    public void processOrder(Order order) {
        if (inventoryService.isAvailable(order.getItem())) {
            // process the order
        }
    }
}

Такая конструкция порождала жёсткую зависимость от конкретной реализации InventoryServiceImpl. Усложняла тестирование из-за невозможности просто подставить мок. В случае необходимости замены реализации, приходилось менять код класса.

«Ручной» DI (ещё без Spring)

Опытные команды уже тогда практиковали IoC/DI — просто делали это вручную:

public class OrderService {
    private final InventoryService inventoryService;

    public OrderService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    public void processOrder(Order order) {
        if (inventoryService.isAvailable(order.getItem())) {
            // process the order
        }
    }
}

Теперь зависимость передаётся извне, и мы можем легко подставить любую реализацию.

Минус такого подхода в том, что в больших проектах приходилось вручную создавать и “связывать” десятки, а то и сотни объектов, что в свою очередь приводило к ошибкам во время написания кода.

Как стало со Spring

Spring предложил IoC-контейнер, который:

  • сам создаёт бины (объекты),
  • управляет их жизненным циклом,
  • автоматически внедряет зависимости.
@Service
public class OrderService {
    private final InventoryService inventoryService;

    @Autowired
    public OrderService(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    public void processOrder(Order order) {
        if (inventoryService.isAvailable(order.getItem())) {
            // process the order
        }
    }
}

Что изменилось:

  • Не нужно вручную создавать InventoryService и передавать его в OrderService.
  • Spring на старте приложения найдёт все реализации InventoryService и автоматически внедрит нужную.
  • При смене реализации достаточно зарегистрировать новый бин — код OrderService менять не придётся.

Почему это “выстрелило” в 2000-х

За счёт автоматизации DI и IoC Spring добился следующих результатов:

  • Масштабируемость — контейнер сам создаёт и связывает сотни объектов.
  • Гибкость — легко подменять реализации через конфигурацию, а не через переписывание кода.
  • Сокращение рутины — меньше ручного кода для “установки” зависимостей.
  • Постепенное внедрение — можно было использовать Spring только для IoC/DI, не переписывая весь проект.

DI и IoC были известны и до Spring, но именно Spring сделал их массовыми и удобными для повседневной Java-разработки, убрав рутину и минимизировав человеческий фактор при “установке” зависимостей.