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

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

Внедрение зависимостей - это основная функциональность, предоставляемая Spring IOC (инверсия управления). Модуль Spring-Core отвечает за внедрение зависимостей с помощью методов Constructor или Setter. Принцип проектирования инверсии управления подчеркивает, что классы Java независимы друг от друга, а контейнер освобождает их от создания и обслуживания объектов. Эти классы, управляемые Spring, должны соответствовать стандартному определению Java-Bean. Внедрение зависимостей в Spring также обеспечивает с

Необходимость внедрения зависимостей

Предположим, что классу One нужен объект класса Two для создания экземпляра метода или работы с ним, тогда говорят, что класс One зависит от класса Two. Хотя может показаться нормальным зависеть от одного модуля от другого, но в реальном мире это может привести к множеству проблем, включая сбой системы. Следовательно, таких зависимостей нужно избегать.

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

Типы внедрения зависимостей Spring

Существует два типа внедрения зависимостей Spring.

  1. Внедрение зависимостей установщика (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;
        }
    }
    
  2. Конструктор инъекции зависимостей (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, не мешают основной бизнес-логике.)