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

89c029aae37c53cb071caa366b71b5c471d92daefe1a5588a50db357b9623e01Spring в первую очередь известен как контейнер, реализующий шаблон проектирования «Внедрение зависимостей».

Не вдаваясь в подробности, внедрение зависимостей можно описать как связывание компонентов приложения во время исполнения. Другими словами, если бину ServiceA нужен для работы бин, имеющий тип RepositoryB, Spring сконструирует этот бин и внедрит его в бин ServiceA.

Подготовка

Поскольку Spring поддерживает несколько вариантов конфигурации контекста, создадим многомодульный maven проект, по одному модулю на каждый вариант. Из зависимостей понадобятся сам Spring, JavaEE из-за JSR-299 и в этот раз можно обойтись без тестов.

Внедрение с помощью конструктора

Наиболее очевидный вариант внедрения, когда зависимости передаются в конструктор класса:

Ничего Spring специфичного тут пока нет. Использование этих классов в Spring отличается для каждого метода инициализации.

Для JavaConfig всё выглядит как обыкновенный java код:

Надо понимать, что методы с аннотацией @Bean перерабатываются Spring’ом и возвращают полностью инициализированные бины, а не просто «сырые» объекты. Например, если использовать baseService() для инициализации более чем одного бина, они все получат один и тот же экземпляр класса BaseService.

Для XML конфигурации код будет немного другой:

В XML можно передавать бины в конструктор как позиционно, так и по имени параметра. Кроме того, можно передавать и по типу параметра:

Бины можно как создавать заранее и ссылаться на них позднее (разумеется, более чем один раз), так и создавать сразу по месту. А можно передавать не бины, а какие либо значения.

Конфигурация groovy отличается не сильно:

Однако создать бин по месту тут нельзя и передавать параметры в конструктор можно только позиционно.

Использование аннотаций требует изменения кода сервисов:

Аннотации @Service и @Repository специфичны для Spring и описывают разные типы Spring бинов.  Аннотация @Inject является стандартной аннотацией JSR-299  и указывает, что параметры конструктора должны быть внедрены при его создании. Внедрение, в данном случае, производится по типам.

Внедрение с помощью сеттеров

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

Начнём с java config как с самого простого:

Класс конфигурации теперь явно задаёт значения полей:

Для XML дескриптор тоже меняется:

К свойствам класса можно обращаться только по имени, то есть внедрение по типам не поддерживается.

В Groovy конфигурации к бинам добавляются имена полей, всё остальное без изменений:

Отличие в инициализации с помощью аннотаций в том, что аннотации  @Inject применяются теперь к сеттерам:

Непосредственное внедрение

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

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

Всё! Аннотация @Inject  применяется непосредственно к полям, а далее действует особая магия Spring.

Как же связывать?

Внедрение зависимостей напрямую выглядит наиболее удобным методом, требующим меньше всего затрат. Однако у этого метода есть недостаток: поскольку связывание производится через reflection, создание бина несколько замедляется. Если бин создаётся один раз при старте приложения, это не страшно. Но если он пересоздаётся, например, для каждого запроса, это может начать сказываться на общей производительности приложения.

Документация по Spring рекомендует использоваться конструкторы и сеттеры, если у бина есть обязательные и опциональные зависимости. Первые связываются через конструктор, вторые, соответственно, через сеттеры.

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

Так же внедрение через конструкторы подвержено проблеме с замкнутыми зависимостями, когда бин А зависит от бина Б, который зависит от бина А. Если Spring столкнётся с такой зависимостью при внедрении через конструкторы, он выбросит исключение BeanCurrentlyInCreationException, а при использовании сеттеров бины будут успешно сконструированы.

Код примера доступен на github.