JavaRush /Курсы /Модуль 5. Spring /Архитектура Spring: IoC, DI, Spring Beans

Архитектура Spring: IoC, DI, Spring Beans

Модуль 5. Spring
1 уровень , 1 лекция
Открыта

Сегодня мы познакомимся с тремя китами, на которых строится Spring: IoC (инверсия управления), DI (внедрение зависимостей) и Spring Beans. Всё это звучит как магия, но на деле — это лучшие друзья любого Java-разработчика.

1. Инверсия управления (Inversion of Control)

Вспомним старую добрую объектно-ориентированную разработку. В объектно-ориентированном программировании часто возникает ситуация, когда один объект зависит от другого. Обычно разработчик сам создает зависимый объект прямо в коде:


public class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine();
    }
}

Вроде работает, но есть проблема: вы "жёстко связали" объект Car с объектом Engine. Если вам нужно подставить другой тип двигателя, например, ElectricEngine, придётся лезть в код Car и его менять. Не слишком-то удобно, да и поломать что-то случайно можно! Такое изменение нарушает принцип гибкости и поддержки кода.

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

Контейнер IoC в Spring

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

Пример:


public class Car {
    private Engine engine;

    // Зависимость передаётся через конструктор
    public Car(Engine engine) {
        this.engine = engine;
    }
}

Теперь машина Car не знает, какой именно двигатель Engine она получит. Это решает контейнер IoC, освобождая нас от головной боли. В данном коде самого контейнера IoC нет — это просто пример класса, который готов к использованию IoC-контейнера.

2. Dependency Injection (DI): ключ к управлению зависимостями

Dependency Injection (DI) — это способ реализации IoC. Контейнер Spring внедряет зависимости (например, Engine) в нужный объект (Car), используя один из следующих подходов:

  1. Через конструктор.
  2. Через сеттер (метод).
  3. Через поле (аннотация).

На практике DI выглядит как магия. Например, так:


@Component
public class Engine {
    // Класс двигателя
}

@Component
public class Car {
    private Engine engine;

    // Spring автоматически внедрит зависимость через конструктор
    @Autowired
    public Car(Engine engine) {
        this.engine = engine;
    }
}

Spring видит, что класс Car зависит от Engine, и автоматически создаёт Engine, передавая его в Car. Вам не нужно самим заботиться о создании или передаче объектов!


3. Spring Beans: строим магию контролируемо

Spring Bean — это объект, управляемый IoC-контейнером. Именно контейнер создаёт бины, настраивает их, "сводит" их зависимости и управляет их жизненным циклом. Если вам нужен объект, просто скажите контейнеру: "Эй, дай мне бин!", и он выполнит вашу просьбу.

Класс можно превратить в бин, если пометить его особыми аннотациями:

  • @Component — универсальная аннотация для создания бинов.
  • @Service — помечает бины, которые выполняют бизнес-логику.
  • @Repository — для бинов, работающих с данными (например, репозитории JPA).
  • @Controller — для бинов, которые обрабатывают HTTP-запросы (в контексте Spring MVC).

Пример создания бина


@Component
public class Engine {
    // Это бин Spring. Теперь контейнер будет управлять этим объектом.
}

@Component
public class Car {
    private final Engine engine;

    @Autowired // Говорим Spring автоматически внедрить зависимость
    public Car(Engine engine) {
        this.engine = engine;
    }
}

4. Способы внедрения зависимостей

Теперь давайте разберём три основных способа внедрения зависимостей с помощью DI.

  1. Конструкторное внедрение

Этот способ предпочтителен, так как он делает зависимости очевидными и immutable. Вот пример:


@Component
public class Car {
    private final Engine engine;

    @Autowired
    public Car(Engine engine) {
        this.engine = engine;
    }
}
  1. Сеттерное внедрение

Плюс такого подхода — гибкость. Зависимость можно заменить после создания объекта. А минус в том, что это может привести к изменению состояния объекта, что не всегда хорошо.


@Component
public class Car {
    private Engine engine;

    @Autowired
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}
  1. Внедрение через поле (аннотации)

Самый спорный способ, поскольку нарушает принцип инкапсуляции. Но для простых случаев — вполне жизнеспособный вариант.


@Component
public class Car {
    @Autowired
    private Engine engine;
}

5. Жизненный цикл Spring Bean'ов

  1. Создание — контейнер создаёт объект.
  2. Инициализация — контейнер передаёт зависимости и вызывает методы, такие как @PostConstruct.
  3. Использование — бин готов работать.
  4. Завершение — контейнер завершает работу бина и вызывает методы, такие как @PreDestroy.

Методы для управления жизненным циклом:

  • @PostConstruct — метод вызывается после создания бина и завершения DI.
  • @PreDestroy — метод вызывается перед уничтожением бина.

Пример кода:


@Component
public class Car {

    @PostConstruct
    public void init() {
        System.out.println("Car is ready to drive!");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Car is being destroyed...");
    }
}

6. Частые ошибки при работе с IoC и DI

Не беспокойтесь, ошибки бывают у всех. Вот классический случай: вы забыли пометить класс Engine аннотацией @Component. Из-за этого Spring не может найти бин:


Error creating bean with name 'car': Unsatisfied dependency expressed through constructor

Подсказка: проверьте, все ли зависимости "включены" в контекст, то есть имеют аннотации типа @Component.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ