Перейти к основному содержимому

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

· 7 мин. чтения

1. Обзор

В этом руководстве мы представим концепции IoC (инверсия управления) и DI (внедрение зависимостей), а также посмотрим, как они реализованы в среде Spring.

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

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

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

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

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

Мы можем достичь инверсии управления с помощью различных механизмов, таких как: шаблон проектирования стратегии, шаблон локатора сервисов, шаблон фабрики и внедрение зависимостей (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 интерфейс ApplicationContext представляет контейнер IoC. Контейнер Spring отвечает за создание, настройку и сборку объектов, известных как bean -компоненты , а также за управление их жизненными циклами.

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

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

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

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

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

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

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

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

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-компонента с одноэлементной областью действия по умолчанию Spring сначала проверяет, существует ли уже кэшированный экземпляр bean-компонента, и создает новый только в том случае, если это не так. Если мы используем область действия прототипа , контейнер возвращает новый экземпляр компонента для каждого вызова метода.

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

<bean id="item1" class="org.foreach.store.ItemImpl1" /> 
<bean id="store" class="org.foreach.store.Store">
<constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" />
</bean>

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

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

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

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

<bean id="store" class="org.foreach.store.Store">
<property name="item" ref="item1" />
</bean>

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

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

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

public class Store {
@Autowired
private Item item;
}

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

Мы также можем добиться этого с помощью XML-конфигурации .

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

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

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

8. Автомонтаж зависимостей

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

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

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

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

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

private Item item;

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

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

public class Store {

@Autowired
private Item item;
}

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

public class Store {

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

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

<bean id="store" class="org.foreach.store.Store" autowire="byType"> </bean>

Затем давайте добавим элемент с именем bean - компонента в свойство item bean-компонента хранилища по имени через XML:

<bean id="item" class="org.foreach.store.ItemImpl1" />

<bean id="store" class="org.foreach.store.Store" autowire="byName">
</bean>

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

9. Ленивые инициализированные компоненты

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

<bean id="item1" class="org.foreach.store.ItemImpl1" lazy-init="true" />

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

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

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

Подробнее об этих концепциях можно прочитать в статьях Мартина Фаулера:

Кроме того, мы можем узнать о реализациях IoC и DI в Spring в справочной документации Spring Framework .