Введение в инверсию управления и внедрение зависимостей с помощью Spring

Введение в инверсию управления и инжекцию зависимостей с помощью пружины

1. обзор

В этой статье мы познакомимся с концепциями IoC (Inversion of Control) и DI (Dependency Injection), а затем рассмотрим, как они реализованы в среде Spring.

Дальнейшее чтение:

Проводка весной: @Autowired, @Resource и @Inject

В этой статье будет проведено сравнение и сопоставление использования аннотаций, связанных с внедрением зависимостей, а именно аннотаций @Resource, @Inject и @Autowired.

Read more

@Component vs @Repository и @Service in Spring

Узнайте о различиях между аннотациями @Component, @Repository и @Service и о том, когда их использовать.

Read more

2. Что такое инверсия управления?

Инверсия управления - это принцип в разработке программного обеспечения, при котором управление объектами или частями программы передается в контейнер или среду. Чаще всего он используется в контексте объектно-ориентированного программирования.

В отличие от традиционного программирования, в котором наш пользовательский код выполняет вызовы библиотеки, IoC позволяет инфраструктуре управлять потоком программы и выполнять вызовы нашего пользовательского кода. Чтобы включить это, фреймворки используют абстракции со встроенным дополнительным поведением. If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.с

Преимущества этой архитектуры:

  • отделить выполнение задачи от ее реализации

  • облегчая переключение между различными реализациями

  • большая модульность программы

  • более легкое тестирование программы путем изоляции компонента или проверки его зависимостей и обеспечения взаимодействия компонентов через контракты

Инверсия управления может быть достигнута с помощью различных механизмов, таких как: шаблон разработки стратегии, шаблон локатора служб, шаблон фабрики и внедрение зависимостей (DI).

Далее мы рассмотрим DI.

3. Что такое внедрение зависимостей?

Внедрение зависимостей - это шаблон для реализации IoC, где инвертируемый элемент управления - это установка зависимостей объекта.

Акт соединения объектов с другими объектами или «инъекция» объектов в другие объекты выполняется ассемблером, а не самими объектами.

Вот как можно создать объектную зависимость в традиционном программировании:

public class Store {
    private Item item;

    public Store() {
        item = new ItemImpl1();
    }
}

В приведенном выше примере нам нужно создать экземпляр реализации интерфейсаItem в самом классеStore.

Используя DI, мы можем переписать пример без указания реализацииItem, которую мы хотим:

public class Store {
    private Item item;
    public Store(Item item) {
        this.item = item;
    }
}

В следующих разделах мы увидим, как мы можем обеспечить реализациюItem с помощью метаданных.

И IoC, и DI - это простые концепции, но они имеют глубокое значение в том, как мы структурируем наши системы, поэтому их стоит хорошо понять.

4. Контейнер Spring IoC

Контейнер IoC - это общая характеристика сред, реализующих IoC.

В среде Spring контейнер IoC представлен интерфейсомApplicationContext. Контейнер Spring отвечает за создание, настройку и сборку объектов, известных какbeans, а также за управление их жизненным циклом.

Инфраструктура Spring предоставляет несколько реализаций интерфейсаApplicationContext -ClassPathXmlApplicationContext иFileSystemXmlApplicationContext для автономных приложений иWebApplicationContext для веб-приложений.

Для сборки bean-компонентов контейнер использует метаданные конфигурации, которые могут быть в форме конфигурации XML или аннотаций.

Вот один из способов вручную создать экземпляр контейнера:

ApplicationContext context
  = new ClassPathXmlApplicationContext("applicationContext.xml");

Чтобы установить атрибутitem в приведенном выше примере, мы можем использовать метаданные. Затем контейнер будет читать эти метаданные и использовать их для сборки bean-компонентов во время выполнения.

Внедрение зависимостей в Spring может выполняться через конструкторы, сеттеры или поля.

5. Внедрение зависимостей на основе конструктора

В случаеconstructor-based dependency injection контейнер вызовет конструктор с аргументами, каждый из которых представляет зависимость, которую мы хотим установить.

Spring разрешает каждый аргумент в основном по типу, за которым следует имя атрибута и индекс для устранения неоднозначности. Давайте посмотрим на конфигурацию bean-компонента и его зависимости с помощью аннотаций:

@Configuration
public class AppConfig {

    @Bean
    public Item item1() {
        return new ItemImpl1();
    }

    @Bean
    public Store store() {
        return new Store(item1());
    }
}

Аннотация@Configuration указывает, что класс является источником определений bean-компонентов. Также мы можем добавить его в несколько классов конфигурации.

Аннотация@Bean используется для метода определения bean-компонента. Если мы не укажем собственное имя, в качестве имени компонента по умолчанию будет использоваться имя метода.

Для bean-компонента с областью действияsingleton по умолчанию Spring сначала проверяет, существует ли уже кэшированный экземпляр bean-компонента, и создает новый только в том случае, если его нет. Если мы используем областьprototype, контейнер возвращает новый экземпляр компонента для каждого вызова метода.

Другой способ создать конфигурацию bean-компонентов - через конфигурацию XML:



    

6. Внедрение зависимостей на основе установщика

Для основанного на установщике DI контейнер будет вызывать методы установки нашего класса после вызова конструктора без аргументов или статического метода фабрики без аргументов для создания экземпляра компонента. Давайте создадим эту конфигурацию с помощью аннотаций:

@Bean
public Store store() {
    Store store = new Store();
    store.setItem(item1());
    return store;
}

Мы также можем использовать XML для той же конфигурации bean-компонентов:


    

Типы инъекций на основе конструктора и сеттера могут быть объединены для одного и того же компонента. В документации Spring рекомендуется использовать внедрение на основе конструктора для обязательных зависимостей и внедрение на основе установки для необязательных.

7. Полевой Внедрение зависимости

В случае DI на основе поля мы можем внедрить зависимости, пометив их аннотацией@Autowired:

public class Store {
    @Autowired
    private Item item;
}

При создании объектаStore, если нет конструктора или метода установки для внедрения bean-компонентаItem, контейнер будет использовать отражение для вставкиItem вStore.

Мы также можем добиться этого с помощьюXML configuration.

Этот подход может выглядеть проще и чище, но его не рекомендуется использовать, поскольку он имеет несколько недостатков, таких как:

  • Этот метод использует отражение для внедрения зависимостей, что обходится дороже, чем внедрение на основе конструктора или установки

  • Используя этот подход, действительно легко добавить несколько зависимостей. Если бы вы использовали внедрение конструктора с несколькими аргументами, это заставило бы нас думать, что класс делает больше, чем одно, что может нарушить принцип единой ответственности.

Более подробную информацию об аннотации@Autowired можно найти в статьеWiring In Spring.

8. Зависимости автоматического подключения

Wiring позволяет контейнеру Spring автоматически разрешать зависимости между сотрудничающими bean-компонентами путем проверки определенных bean-компонентов.

Существует четыре режима автоматической разметки компонента с использованием конфигурации XML:

  • no: значение по умолчанию - это означает, что для bean-компонента не используется автоматическое подключение, и мы должны явно указать зависимости

  • Автоматическое подключениеbyName: выполняется на основе имени свойства, поэтому Spring будет искать bean-компонент с тем же именем, что и свойство, которое необходимо установить.

  • byType: аналогичен автоматическому подключениюbyName, только в зависимости от типа свойства. Это означает, что Spring будет искать bean-компонент с тем же типом свойства, которое нужно установить. Если существует несколько bean-компонентов этого типа, фреймворк выдает исключение.

  • Автоматическое подключениеconstructor: выполняется на основе аргументов конструктора, что означает, что Spring будет искать bean-компоненты с тем же типом, что и аргументы конструктора.

Например, давайте автоматически подключим bean-компонентitem1, определенный выше по типу, к bean-компонентуstore:

@Bean(autowire = Autowire.BY_TYPE)
public class Store {

    private Item item;

    public setItem(Item item){
        this.item = item;
    }
}

Мы также можем внедрить beans с помощью аннотации@Autowired для автоматического подключения по типу:

public class Store {

    @Autowired
    private Item item;
}

Если имеется несколько bean-компонентов одного типа, мы можем использовать аннотацию@Qualifier для ссылки на bean-компонент по имени:

public class Store {

    @Autowired
    @Qualifier("item1")
    private Item item;
}

Теперь давайте автоматически подключим bean-компоненты по типу с помощью конфигурации XML:

 

Затем давайте введем компонент с именемitem в свойствоitem компонентаstore по имени через XML:




Мы также можем переопределить автопроводку, определив зависимости явно через аргументы конструктора или установщики.

9. Ленивые инициализированные бобы

По умолчанию контейнер создает и настраивает все одноэлементные компоненты во время инициализации. Чтобы избежать этого, вы можете использовать атрибутlazy-init со значениемtrue в конфигурации bean-компонента:

Как следствие, компонентitem1 будет инициализирован только при первом запросе, а не при запуске. Преимущество этого состоит в более быстром времени инициализации, но компромисс заключается в том, что ошибки конфигурации могут быть обнаружены только после запроса компонента, что может занять несколько часов или даже дней после того, как приложение уже запущено.

10. Заключение

В этой статье мы представили концепции инверсии управления и внедрения зависимостей и продемонстрировали их на примерах в среде Spring.

Вы можете узнать больше об этих концепциях в статьях Мартина Фаулера:

И вы можете узнать больше о реализациях Spring IoC и DI вSpring Framework Reference Documentation.