- Способы внедрения зависимостей (Dependency Injection) в Spring
- Конфигурация Maven
- Конфигурация бинов с помощью аннотаций
- Constructor Based Injection
- Setter Based Injection
- Конфигурация бинов с XML
- Constructor Based Injection
- Setter-Based Injection
- Какой способ внедрения зависимости лучше
- Способы внедрения зависимостей (Dependency Injection) в Spring: 3 комментария
- Внедрение зависимостей DI в Java Spring
- Что такое внедрение зависимостей
- Необходимость внедрения зависимостей
- Типы внедрения зависимостей Spring
- Пример Spring DI
- Резюме
- Внедрение зависимостей через поля — плохая практика
- Типы внедрений
- Конструктор
- Сеттер
- Что не так?
- Нарушение принципа единственной ответственности
- Сокрытие зависимостей
- Зависимость от DI-контейнера
- Неизменность
- Внедрение через конструктор vs сеттер
- Сеттеры
- Конструкторы
Способы внедрения зависимостей (Dependency Injection) в Spring
Dependency Injection (внедрение зависимостей) – ключевой шаблон проектирования в Spring. Мы говорим фреймворку создать за нас бины (иначе говоря — объекты) и внедрить их в другие бины. И фреймворк это делает.
Но как объяснить фреймворку Spring, что такой-то бин должен стать зависимостью для другого бина? Вариантов немного, а самых частых всего два: бин внедряется либо через конструктор класса, либо с помощью сеттера. Первое называется constructor-based injection, а второе — setter-based injection.
В этой статье мы создадим бин Engine и будем внедрять его в два других бина: в бин CarWithConstructor с помощью конструктора и в CarWithSetter с помощью сеттера.
Конфигурация Maven
Чтобы начать работу с бинами, необходимо добавить в pom.xml зависимость:
Определим классы. Итак, сначала у нас есть три класса. Класс Engine:
Класс CarWithConstructor с конструктором:
И класс CarWithSetter с сеттером:
Чтобы внедрить бин, классов нам недостаточно, Spring имеет дело с бинами, а не классами. Поэтому нужно сконфигурировать эти классы так, чтобы Spring контейнер создал на их основе бины. В конфигурации заодно будут заданы и pзависимости. Конфигурировать бины можно либо с помощью аннотаций, либо с помощью XML. (Но учтите, что XML-конфигурация немного устарела.)
Конфигурация бинов с помощью аннотаций
До того как внедрять бин engine, давайте его определим:
Аннотация @Component говорит фреймворку превратить класс в бин. При запуске Spring создаст экземпляр класса Engine. Этот экземпляр будет синглтоном в нашем случае. Мы сможем его впоследствии получить из контекста приложения с помощью команды:
И он будет внедрен во все бины, где мы зададим его в качестве зависимости. Неважно каким способом – через конструктор или сеттер.
Давайте зададим пакет, в котором хранятся бины, чтобы Spring знал, где их искать. Это делается с помощью аннотации @ComponentScan:
Обычно в классе Config прописываются конфигурации, но в нашем простом приложении он пуст.
В пакете «ru.javalang.injection» Spring будет искать аннотированные с помощью @Component классы, чтобы превратить их в бины при запуске приложения и инициализации контейнера Spring.
Итак, мы определили один бин engine. Теперь можно его внедрять в другие бины. Конечно, эти другие бины тоже надо сконфигурировать. И внутри конфигурации задать зависимости (dependency injection).
Constructor Based Injection
Если в классе есть конструктор, то можно внедрить зависимость через конструктор. При создании класса контейнер Spring вызовет конструктор и передаст зависимость в качестве аргумента конструктора.
Давайте определим бин CarWithConstructor и внедрим в него бин Engine с помощью конструктора:
Аннотация @Component означает, что класс CarWithConstructor надо зарегистрировать в качестве бина.
А аннотация @Autowired перед конструктором говорит фреймворку внедрить бин engine в качестве зависимости в бин CarWithConstructor.
Обратите внимание, что начиная с версии Spring 4.3 аннотацию @Autowired можно опустить, если у класса всего один конструктор. О том, что в конструкторе надо внедрить бин, фреймворк догадается сам.
Setter Based Injection
Если в классе задан сеттер, то зависимость можно внедрить и через него. Тогда при создании экземпляра класса контейнер вызовет конструктор без аргументов, а потом сеттер, чтобы внедрить зависимость во только что созданный бин.
Определим бин CarWithSetter и внедрим в него бин engine с помощью сеттера.
Для этого используем перед сеттером аннотацию @Autowired:
Так же как в предыдущем случае, аннотацию @Autowired перед сеттером можно опустить.
Более того, можно опустить и сеттер. И просто аннотировать поле car:
И внедрение зависимости все равно произойдет. Несмотря на то, что тут нет ни конструктора, ни сеттера, а поле car имеет модификатор private. Это возможно, потому что под капотом фреймворк использует рефлексию для создания бинов.
Чтобы получить экземпляры машин, надо обратиться к контексту приложения:
Переменная carWithConstructor будет иметь ненулевую ссылку на engine. Хотя мы не создавали ни один объект с помощью оператора new. Все бины создал фреймворк и добавил ссылки на зависимости там, где они были определены.
Обратите внимание, что все бины у нас синглтоны, и обе переменные carWithConstructor и carWithSetter ссылаются на один и тот же engine. Сингтон — самый частый жизненный цикл бина.
Конфигурация бинов с XML
А теперь сконфигурируем все то же самое с помощью XML:
Тег bean задает бин, это аналог аннотации @Component.
Constructor Based Injection
Вот часть вышеприведенного XML, которая определяет бин CarWithConstructor:
Тут constructor-arg определяет внедрение зависимости с помощью конструктора.
Атрибут ref содержит ссылку на идентификатор бина engine.
Setter-Based Injection
А это часть вышеприведенного XML, которая задает бин CarWithSetter:
Здесь тег property задает внедрение зависимости с помощью сеттера.
Обратите внимание, что если мы конфигурируем бины с помощью XML, то задать сеттер в классе необходимо. Иначе будет выброшено исключение. Потому что все послабления в конфигурациях пришли с аннотациями, с XML все гораздо строже.
За контекст, созданный с помощью XML, отвечает другой класс:
Бин из XML-контекста получаем аналогично:
Убедимся, что engine внедрен:
Какой способ внедрения зависимости лучше
Для разработчика большой разницы нет. В документации рекомендуется отталкиваться от класса – его структуры и цели. Если зависимость обязательна в данном классе, то логичнее это поле передавать в конструкторе. А значит это будет внедрение через конструктор. Соответственно если какая-то зависимость необязательна, то внедряем ее через сеттер.
Код примера есть на GitHub.
Способы внедрения зависимостей (Dependency Injection) в Spring: 3 комментария
Большое спасибо вам за ваши статьи. Все очень коротко, ястно и лаконично объясняется…Однозначно лучше чем любой видео-урок на ютубе.
Спасибо, будем продолжать). Правда, как раз хочу записать краткие видео-уроки.
Огромная благодарность Автору статей по Spring, это самые ясные и лаконичные разъяснения работы фреймворка, которые я нашел!
Искренне Ваш, Yustas
Источник
Внедрение зависимостей DI в Java Spring
Что такое внедрение зависимостей
Внедрение зависимостей — это основная функциональность, предоставляемая Spring IOC (инверсия управления). Модуль Spring-Core отвечает за внедрение зависимостей с помощью методов Constructor или Setter . Принцип проектирования инверсии управления подчеркивает, что классы Java независимы друг от друга, а контейнер освобождает их от создания и обслуживания объектов. Эти классы, управляемые Spring, должны соответствовать стандартному определению Java-Bean. Внедрение зависимостей в Spring также обеспечивает с
Необходимость внедрения зависимостей
Предположим, что классу One нужен объект класса Two для создания экземпляра метода или работы с ним, тогда говорят, что класс One зависит от класса Two . Хотя может показаться нормальным зависеть от одного модуля от другого, но в реальном мире это может привести к множеству проблем, включая сбой системы. Следовательно, таких зависимостей нужно избегать.
Spring IOC разрешает такие зависимости с помощью внедрения зависимостей, что упрощает тестирование и повторное использование кода. Слабая связь между классами может быть возможна путем определения интерфейсов для общих функций, и инжектор создаст экземпляры объектов требуемой реализации. Задача создания экземпляров объектов выполняется контейнером в соответствии с конфигурациями, указанными разработчиком.
Типы внедрения зависимостей Spring
Существует два типа внедрения зависимостей Spring.
- Внедрение зависимостей установщика (SDI): это более простой из двух методов DI. В этом случае DI будет вводиться с помощью методов setter и/или getter . Теперь, чтобы установить DI как SDI в bean-компоненте, это делается с помощью файла конфигурации bean-компонента. Для этого свойство, которое должно быть установлено с помощью SDI, объявляется в теге
в файле bean-config.
Пример: Допустим, есть класс GFG, который использует SDI и задает свойства гиков. Код для него приведен ниже.
Конструктор инъекции зависимостей (CDI): В этом примере DI вводится с помощью конструктора. Установить DI как CDI в bean-компоненте можно через файл конфигурации bean-компонента. Для этого свойство, которое должно быть установлено с помощью CDI, объявляется в теге в файле bean-config.
Пример: возьмем тот же пример, что и SDI.
Внедрение зависимостей установщика (SDI) против внедрения зависимостей конструктора (CDI)
Сеттер DI | Конструктор DI |
---|---|
Плохая читаемость, так как в приложение добавляется множество кода шаблонов. | Хорошая читабельность, так как это отдельно присутствует в коде. |
Компонент должен включать методы получения и установки для свойств. | Класс bean-компонента должен объявить соответствующий конструктор с аргументами. В противном случае будет выброшено исключение BeanCreationException . |
Требует добавления аннотации @Autowired над установщиком в коде и, следовательно, увеличивает связь между классом и контейнером DI. | Лучше всего в случае слабой связи с контейнером DI, так как не требуется даже добавлять аннотацию @Autowired в код (неявные инъекции конструктора для сценариев с одним конструктором после Spring 4.0) |
При использовании Setter DI возникают круговые или частичные зависимости, поскольку создание объекта происходит до инъекций. | Нет возможности для циклической или частичной зависимости, поскольку зависимости разрешаются до создания самого объекта. |
Предпочтительный вариант, когда свойства меньше и можно создавать изменяемые объекты. | Предпочтительный вариант, когда свойств компонента больше, а неизменяемые объекты (например, финансовые процессы) важны для приложения. |
Пример Spring DI
Мы использовали три класса и интерфейс в качестве компонентов, чтобы проиллюстрировать концепции CDI и SDI. Это классы Vehicle, ToyotaEngine, Tires и интерфейс IEngine соответственно.
В нашем примере мы видим, что класс Vehicle зависит от реализации Engine, который является интерфейсом. (Таким образом, в основном производителю транспортных средств требуется стандартный двигатель, соответствующий индийским нормам выбросов.) Класс ToyotaEngine реализует интерфейс, и его ссылка предоставляется в файле конфигурации bean-компонента, сопоставленном с одним из свойств класса транспортного средства.
В классе Vehicle мы вызываем контекст приложения, и выполняется создание экземпляра компонента. Созданы два объекта класса Vehicle. obj1 создается через bean-компонент с именем InjectwithConstructor . Имя bean-компонента может находиться в файле конфигурации bean-компонента. Аналогичным образом создается экземпляр obj2 через bean-компонент с именем InjectwithSetter . Можно заметить, что obj1 вводится через конструктор, а obj2 использует установщик. В приведенном ниже файле конфигурации bean-компонентов мы использовали два объявления bean-компонентов.
Компонент InjectwithConstructor использует аргумент-конструктор элемента с именем атрибута и ссылкой. Атрибут Name коррелирует с именем аргумента конструктора, указанным в определении класса Vehicle. Атрибут ref указывает на ссылку bean-компонента, которую можно использовать для инъекции.
InjectwithSetter использует элемент свойства, чтобы указать «имя» свойства и «значение» свойства. Вместо значения атрибут ref может использоваться для обозначения ссылки на компонент.
В деталях конфигурации мы вводим ссылку ToyotaBean в ссылку IEngine в аргументе конструктора класса Vehicle , где IEngine является интерфейсом и нуждается в ссылке на реализующий класс для внедрения bean-компонента. Мы использовали две отдельные ссылки на bean-компоненты для класса Tires для внедрения через сеттер и конструктор соответственно. Мы можем заметить, что tyre1Bean и tyre2Bean инициализируются значениями строковых литералов для каждого из свойств.
pom.xml
Enigne.java
ToyotaEngine.java
Tyres.java
Vehicle.java
springContext.xml
Процесс создания экземпляра bean-компонента и внедрения зависимостей показан на рисунке ниже:
Резюме
Как обсуждалось выше, избегайте использования инъекции полей, так как это только обеспечивает лучшую читаемость, несмотря на многие недостатки. Инъекции сеттеров и конструкторов имеют свои плюсы и минусы, как обсуждалось выше. Поэтому мы должны использовать комбинацию того и другого, что также предлагается самим сообществом Spring.
Используйте внедрение конструктора для обязательных зависимостей и внедрение установщика для дополнительных зависимостей. (Здесь обязательная зависимость — это та, без которой основная бизнес-логика не будет работать, а необязательные зависимости — это те, которые имея значение null , не мешают основной бизнес-логике.)
Источник
Внедрение зависимостей через поля — плохая практика
Внедрение зависимостей через поля является очень популярной практикой в DI-фреймворках, таких как Spring. Тем не менее, у этого метода есть несколько серьезных компромиссов и поэтому стоит чаще избегать его.
Типы внедрений
Есть три основных способа внедрить ваши зависимости в класс: через конструктор, сеттер и поле. Давайте бегло сравним код с одними и теми же зависимостями, внедренными с помощью каждого из подходов.
Конструктор
Сеттер
Что не так?
Как можно наблюдать, вариант внедрения через поле выглядит очень привлекательным. Он очень лаконичен, выразителен, отсутствует шаблонный код. По коду легко перемещаться и читать его. Ваш класс может просто сфокусироваться на основной функциональности и не загромождается шаблонным DI-кодом. Вы просто помещаете аннотацию @Autowired над полем — и все. Не надо писать специальных конструкторов или сеттеров только для того, чтобы DI-контейнер предоставил необходимые зависимости. Java довольно многословна сама по себе, так что стоит использовать любую возможность, чтобы сделать код короче, верно?
Нарушение принципа единственной ответственности
Добавлять новые зависимости просто. Возможно даже слишком просто. Нет никакой проблемы добавить шесть, десять или даже более зависимостей. При использовании конструкторов для внедрения, после определенного момента число аргументов конструктора становится слишком большим и тут же становится очевидно, что что-то не так. Наличие слишком большого количества зависимостей обычно означает, что у класса слишком много зон ответственности. Это может быть нарушением принципов единственной ответственности (single responsibility) и разделения ответственности (ориг.: separation of concerns) и является хорошим индикатором, что класс возможно стоит более внимательно изучить и подвергнуть рефакторингу. При использовании внедрения через поля такого явного тревожного индикатора нет, и таким образом происходит неограниченное разрастание внедренных зависимостей.
Сокрытие зависимостей
Использование DI-контейнера означает, что класс более не ответственен за управление его зависимостями. Ответственность за их получение выносится из класса во вне и теперь кто-то другой ответственен за их предоставление: это может быть DI-контейнер или ручное предоставление их через тесты. Когда класс более не отвечает за получение зависимостей, он должен явно взаимодействовать с ними, используя публичные интерфейсы — методы или конструкторы. Таким образом становится четко понятно, что требует класс, а также опциональные ли это зависомости (через сеттеры) или обязательные (конструктор)
Зависимость от DI-контейнера
Одна из ключевых идей DI-фреймворков заключается в том, что управляемый класс не должен зависеть от конкретного используемого контейнера. Другими словами, это должен быть простой POJO-класс, экземпляр которого может быть создан самостоятельно, если вы передадите ему все необходимые зависимости. Таким образом, вы можете создать его в юнит-тесте без запуска контейнера и протестировать его отдельно (с контейнером это будет скорее интеграционный тест). Если нет завязки на контейнер, вы можете использовать класс как управляемый или неуправляемый, или даже переключиться на другой DI-фреймворк.
Однако при внедрении прямо в поля вы не предоставляете прямого способа создания экземпляра класса со всеми необходимыми зависимостями. Это означает, что:
- Существует способ (путем вызова конструктора по-умолчанию) создать объект с использованием new в состоянии, когда ему не хватает некоторых из его обязательных зависимостей, и использование приведет к NullPointerException
- Такой класс не может быть использован вне DI-контейнеров (тесты, другие модули) и нет способа кроме рефлексии предоставить ему необходимые зависимости
Неизменность
В отличие от способа с использованием конструктора, внедрение через поля не может использоваться для присвоения зависимостей final-полям, что приводит к тому, что ваши объекты становятся изменяемыми
Внедрение через конструктор vs сеттер
Таким образом, инъекция через поля может не быть хорошим способом. Что остается? Сеттеры и конструкторы. Какой из них следует использовать?
Сеттеры
Команда Spring главным образом выступает за инъекцию через сеттеры, потому что большое количество аргументов конструктора может стать громоздким, особенно если свойства являются необязательными. Сеттеры также делают объекты этого класса пригодными для реконфигурации или повторной инъекции позже. Управление через JMX MBeans является ярким примером
Некоторые пуристы предпочитают инъекцию на основе конструктора. Предоставление всех зависимостей означает, что объект всегда возвращается в вызывающий код в полностью инициализированном состоянии. Недостатком является то, что объект становится менее поддающимся реконфигурации и повторной инъекции
Конструкторы
Инъекция через конструкторы хороша для обязательных зависимостей — тех, которые требуются для корректной функциональности объекта. Передавая их через конструктор, вы можете быть уверенными в том, что объект полностью готов к использованию с момента создания. Поля, присвоенные в конструкторе, также могут быть final, что позволяет объекту быть полностью неизменным или как минимум защищает необходимые поля.
Одно из следствий использования внедрения через конструктор — это то что теперь невозможна циклическая зависимость между двумя объектами, созданными таким образом (в отличие от внедрения через сеттер). Это скорее плюс, чем ограничение, поскольку следует избегать циклических зависимостей, что обычно является признаком плохой архитектуры. Таким образом предотвращается подобная практика.
Еще одним преимуществом является то, что при использовании Spring версий 4.3+ вы можете полностью отвязать ваш класс от конкретного DI-фреймворка. Причина в том, что Spring теперь поддерживает неявное внедрение через конструктор для сценариев использования с одним конструктором. Это означает, что вам больше не нужны DI-аннотации в вашем классе. Конечно, вы можете достигнуть того же результата с помощью явного конфигурирования DI в настройках Spring для данного класса; просто сейчас это сделать гораздо проще.
Что касается Spring 4.x, официальная рекомендация из документации изменилась и теперь инъекция через сеттер более не предпочтительна над конструктором:
Команда Spring главным образом выступает за инъекцию через конструктор, поскольку она позволяет реализовывать компоненты приложения как неизменяемые объекты и гарантировать, что требуемые зависимости не null. Более того, компоненты, внедренные через через конструктор, всегда возвращаются в клиентский код в полностью инициализированном состоянии. Как небольшое замечание, большое число аргументов конструктора является признаком «кода с запашком» и подразумевает, что у класса, вероятно, слишком много обязанностей, и его необходимо реорганизовать, чтобы лучше решать вопрос о разделении ответственности.
Инъекция через сеттер должна использоваться в первую очередь для опциональных зависимостей, которым могут быть присвоены значения по-умолчанию внутри класса. В противном случае, проверки на not-null должны быть использованы везде, где код использует эти зависимости. Одно из преимуществ использования внедрения через сеттеры заключается в том, что они делают объекты класса поддающимися реконфигурации и повторному инжектированию позже
Источник