Рубрики
Без рубрики

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

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

Автор оригинала: Loredana Crusoveanu.

1. Обзор

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

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

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

@Component vs @Repository и @Service весной

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

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

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

Преимущества этой архитектуры заключаются в следующем:

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

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

Теперь мы посмотрим на ДИ.

3. Что Такое Инъекция Зависимостей?

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

Соединение объектов с другими объектами или “инъекция” объектов в другие объекты выполняется ассемблером, а не самими объектами.

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

public class Store {
    private Item item;
 
    public Store() {
        item = new ItemImpl1();    
    }
}

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

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

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

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

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

4. Пружинный Контейнер МоК

Контейнер IoC является общей характеристикой фреймворков, реализующих IoC.

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

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

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

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

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

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

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

5. Внедрение Зависимостей На Основе Конструктора

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

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

@Configuration
public class AppConfig {

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

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

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

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

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

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

 
 
     

6. Внедрение Зависимостей На основе Сеттера

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

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

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


    

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

7. Внедрение Зависимостей На местах

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

public class Store {
    @Autowired
    private Item item; 
}

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

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

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

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

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

8. Зависимости Автопроводки

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

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

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

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

@Bean(autowire = Autowire.BY_TYPE)
public class Store {
    
    private Item item;

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

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

public class Store {
    
    @Autowired
    private Item item;
}

Если существует более одного компонента одного и того же типа, мы можем использовать @Квалификатор аннотацию для ссылки на компонент по имени:

public class Store {
    
    @Autowired
    @Qualifier("item1")
    private Item item;
}

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

 

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




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

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

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

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

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

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

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

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