Что такое внедрение зависимостей
Внедрение зависимостей - это основная функциональность, предоставляемая 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, объявляется в теге <property> в файле bean-config.Пример: Допустим, есть класс GFG, который использует SDI и задает свойства гиков. Код для него приведен ниже.
package com.geeksforgeeks.org; import com.geeksforgeeks.org.IGeek; public class GFG { // The object of the interface IGeek IGeek geek; // Setter method for property geek public void setGeek(IGeek geek) { this.geek = geek; } }
- Конструктор инъекции зависимостей (CDI): В этом примере DI вводится с помощью конструктора. Установить DI как CDI в bean-компоненте можно через файл конфигурации bean-компонента. Для этого свойство, которое должно быть установлено с помощью CDI, объявляется в теге <constructor-arg> в файле bean-config.
Пример: возьмем тот же пример, что и SDI.
package com.geeksforgeeks.org; import com.geeksforgeeks.org.IGeek; public class GFG { // The object of the interface IGeek IGeek geek; // Constructor to set the CDI GFG(IGeek geek) { this.geek = geek; } }
Внедрение зависимостей установщика (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
<dependencies>
<!-- https:// mvnrepository.com/artifact
/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<!-- https:// mvnrepository.com/artifact
/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
</dependencies>
Enigne.java
interface IEngine {
String EMISSION_NORMS = "BSIV";
String importOrigin();
double cost();
}
ToyotaEngine.java
public class ToyotaEngine implements IEngine {
String company;
double cost;
public double getCost()
{
return cost;
}
public void setCost(double cost)
{
cost = this.cost;
}
public String getCompany()
{
return company;
}
public void setCompany(String company)
{
this.company = company;
}
@Override
public String importOrigin()
{
return "Japan";
}
@Override
public double cost()
{
return cost;
}
@Override
public String toString()
{
return "This is Engine object from: "
+ company;
}
}
Tyres.java
public class Tyres {
String name;
String place;
String message;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getPlace()
{
return place;
}
public void setPlace(String place)
{
this.place = place;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
@Override
public String toString()
{
return "This is Tyre object: "
+ name + " " + place
+ " " + message;
}
}
Vehicle.java
public class Vehicle {
IEngine engine;
Tyres tyre;
public Tyres getTyre()
{
return tyre;
}
public void setTyre(Tyres tyre)
{
System.out.println("tyre instantiated via setter");
this.tyre = tyre;
}
public Vehicle(IEngine engine, Tyres tyre)
{
System.out.println("instantiated via constructor");
this.engine = engine;
this.tyre = tyre;
}
public Vehicle() {}
public IEngine getEngine()
{
return engine;
}
public void setEngine(IEngine engine)
{
System.out.println("instantiated via setter");
this.engine = engine;
}
@Override
public String toString()
{
return engine + " " + tyre;
}
public static void main(String a[])
{
ApplicationContext rootctx
= new ClassPathXmlApplicationContext(
"springContext.xml");
// Instantiating the obj1 via Constructor DI
Vehicle obj1
= (Vehicle)rootctx
.getBean("InjectwithConstructor");
// Instantiating the obj1 via Setter DI
Vehicle obj2
= (Vehicle)rootctx
.getBean("InjectwithSetter");
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj1 == obj2);
}
}
springContext.xml
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="GFG" class="com.geeksforgeeks.org.GFG">
<constructor-arg>
<bean class="com.geeksforgeeks.org.impl.CsvGFG" />
</constructor-arg>
</bean>
<bean id="CsvGFG" class="com.geeksforgeeks.org.impl.CsvGFG" />
<bean id="JsonGFG" class="com.geeksforgeeks.org.impl.JsonGFG" />
</beans>
Процесс создания экземпляра bean-компонента и внедрения зависимостей показан на рисунке ниже:
Резюме
Как обсуждалось выше, избегайте использования инъекции полей, так как это только обеспечивает лучшую читаемость, несмотря на многие недостатки. Инъекции сеттеров и конструкторов имеют свои плюсы и минусы, как обсуждалось выше. Поэтому мы должны использовать комбинацию того и другого, что также предлагается самим сообществом Spring.
Используйте внедрение конструктора для обязательных зависимостей и внедрение установщика для дополнительных зависимостей. (Здесь обязательная зависимость - это та, без которой основная бизнес-логика не будет работать, а необязательные зависимости - это те, которые имея значение null
, не мешают основной бизнес-логике.)