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 бінів

  1. Створення — контейнер створює об'єкт.
  2. Ініціалізація — контейнер передає залежності і викликає методи, такі як @PostConstruct.
  3. Використання — бін готовий працювати.
  4. Завершення — контейнер завершує роботу біна і викликає методи, такі як @PreDestroy.

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

  • @PostConstruct — метод викликається після створення біна і завершення DI.
  • @PreDestroy — метод викликається перед знищенням біна.

Приклад коду:


@Component
public class Car {

    @PostConstruct
    public void init() {
        System.out.println("Автомобіль готовий до руху!");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Автомобіль знищується...");
    }
}

6. Поширені помилки при роботі з IoC і DI

Не переймайтеся, помилки трапляються в усіх. Ось класичний випадок: ви забули позначити клас Engine анотацією @Component. Через це Spring не може знайти бін:


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

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

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ