JavaRush /Курси /Модуль 5. Spring /Різні способи впровадження залежностей: через конструктор...

Різні способи впровадження залежностей: через конструктори, сеттери, поля

Модуль 5. Spring
Рівень 2 , Лекція 2
Відкрита

Тепер, коли ми знаємо, навіщо нам потрібен DI і як Spring керує залежностями, час заглибитись в різні способи впровадження залежностей. Ми вже трохи про це говорили на попередньому уроці.

Впровадження залежностей — це процес передачі об'єкта (або його посилання) іншому об'єкту, якому він потрібен, замість того, щоб об'єкт сам створював свої залежності. Spring надає три основні способи впровадження залежностей:

  1. Через конструктори.
  2. Через сеттери.
  3. Через поля.

Кожен з цих підходів має свої плюси і мінуси, а вибір конкретного способу залежить від контексту задачі. Давай уважно розберемо кожен з них і подивимось, де і як їх краще застосовувати.


Впровадження залежностей через конструктори

Переваги:

  • Імм'ютебельність: залежність задають під час створення об'єкта, і її не можна змінити після інстанціації.
  • Обов'язковість залежності: якщо конструктор вимагає параметри, Spring обов'язково передасть їх, інакше буде помилка.

Недоліки:

  • Багато параметрів у конструкторі можуть зробити код менш читабельним.
  • При великій кількості залежностей конструктори стають громіздкими.

Приклад:


import org.springframework.stereotype.Component;

// Залежність, яку ми будемо впроваджувати
@Component
public class Engine {
    public void start() {
        System.out.println("Двигун запускається...");
    }
}

// Головний клас
@Component
public class Car {
    private final Engine engine;

    // Впровадження залежності через конструктор
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void drive() {
        engine.start();
        System.out.println("Машина їде...");
    }
}

Як це працює:

Spring автоматично знаходить підходящий бін Engine і передає його в конструктор Car. Конструкторний підхід також дуже зручний, якщо об'єкт повинен бути строго залежним від іншого.


Впровадження залежностей через сеттери

Переваги:

  • Дозволяє задавати залежність після створення об'єкта.
  • Більш гнучкий підхід: можна змінити залежність у runtime (хоча таке потрібно нечасто).

Недоліки:

  • Неочевидно, чи є залежність обов'язковою. Наприклад, якщо ти забув викликати метод-сеттер, додаток може несподівано впасти.
  • Менш підходить для реалізації незмінюваних (immutable) об'єктів.

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


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Залежність, яку ми будемо впроваджувати
@Component
public class Transmission {
    public void engage() {
        System.out.println("Трансмісія задіяна.");
    }
}

// Головний клас
@Component
public class Truck {
    private Transmission transmission;

    // Впровадження залежності через сеттер
    @Autowired
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    public void haul() {
        transmission.engage();
        System.out.println("Вантажівка перевозить вантаж...");
    }
}

Як це працює: Spring викликає метод setTransmission() після створення об'єкта Truck. @Autowired вказує контейнеру IoC, що потрібно впровадити залежність.

Цей спосіб зручний, якщо залежність може змінюватися або якщо об'єкт потребує складної ініціалізації.


Впровадження залежностей через поля

Переваги:

  • Найкомпактніший і лаконічний код.
  • Потрібно менше коду для впровадження залежності.

Недоліки:

  • Для модульного тестування не дуже зручно, бо залежності складно підставити через конструктор.
  • Порушує принцип інкапсуляції, оскільки поля впроваджуються напряму.

Приклад:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Залежність, яку ми будемо впроваджувати
@Component
public class BrakeSystem {
    public void applyBrakes() {
        System.out.println("Гальма застосовано!");
    }
}

// Головний клас
@Component
public class Bicycle {
    @Autowired
    private BrakeSystem brakeSystem; // Впровадження залежності через поле

    public void stop() {
        brakeSystem.applyBrakes();
        System.out.println("Велосипед зупинився.");
    }
}

Як це працює: Spring впроваджує BrakeSystem безпосередньо у приватне поле brakeSystem. Ця магія досягається за допомогою анотації @Autowired.


Порівняння способів впровадження залежностей

Підхід Переваги Недоліки
Конструкторний DI Імм'ютебельність, обов'язковість залежності Незручно для об'єктів з великою кількістю залежностей
Сеттерний DI Гнучкість, підходить для зміни залежностей у runtime Можлива робота без встановленої залежності, не підходить для immutable об'єктів
DI через поля Компактний код, мінімальні зусилля Порушення інкапсуляції, менш зручно для тестів

Практичний приклад: все разом!

Створимо невеликий приклад, де використовуються всі три підходи одночасно.


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// Залежності
@Component
class Processor {
    public void process() {
        System.out.println("Обробка даних...");
    }
}

@Component
class Memory {
    public void load() {
        System.out.println("Пам'ять завантажена!");
    }
}

@Component
class HardDrive {
    public void save() {
        System.out.println("Дані збережено на жорсткому диску.");
    }
}

// Головний клас
@Component
class Computer {
    private final Processor processor; // Впровадження через конструктор
    private Memory memory;             // Впровадження через сеттер
    @Autowired
    private HardDrive hardDrive;       // Впровадження через поле

    @Autowired
    public Computer(Processor processor) {
        this.processor = processor;
    }

    @Autowired
    public void setMemory(Memory memory) {
        this.memory = memory;
    }

    public void start() {
        processor.process();
        memory.load();
        hardDrive.save();
        System.out.println("Комп'ютер працює!");
    }
}

За допомогою цього прикладу бачимо, як можна одночасно використовувати різні способи DI.


Коли використовувати кожен підхід?

  1. Конструкторний DI:

    • Залежність обов'язкова для роботи класу.
    • Об'єкт immutable.
    • Ти хочеш, щоб під час створення об'єкта залежність обов'язково була передана.
  2. Сеттерний DI:

    • Залежність опціональна.
    • Потрібна гнучкість у зміні залежності після інстанціації.
  3. DI через поля:

    • Коли важлива лаконічність коду.
    • Якщо тестування не є пріоритетом.

Підводні камені та типові помилки

При впровадженні залежностей можна натрапити на кілька поширених проблем:

  1. Циклічні залежності. Наприклад, якщо бін A залежить від біна B, а бін B залежить від біна A, Spring не зможе їх ініціалізувати. Щоб вирішити цю проблему, можна скористатися анотацією @Lazy або переглянути архітектуру додатка.

  2. Відсутність залежності. Якщо ти забув вказати бін або @Autowired, Spring викине NoSuchBeanDefinitionException. Завжди перевіряй налаштування конфігурації.

  3. Неоднозначність залежностей. Якщо Spring знаходить кілька бінів одного типу, він не зможе автоматично їх впровадити. Використовуй анотацію @Qualifier, щоб явно вказати потрібний бін.


@Autowired
@Qualifier("specificBeanName")
private MyService myService;

Тепер ти знаєш, як впроваджувати залежності в Spring, і впевнено зможеш вибирати підходящий спосіб для кожного конкретного випадку. У наступній лекції розберемо, як керувати життєвим циклом бінів та додавати додаткові налаштування!

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