1. Введение
Возможно, одним из наиболее важных принципов разработки современного программного обеспечения является внедрение зависимостей (DI),
которое совершенно естественно вытекает из другого критически важного принципа: модульности.
В этом кратком руководстве будет рассмотрен особый тип техники внедрения зависимостей в Spring, называемый внедрением зависимостей на основе конструктора,
что, проще говоря, означает, что мы передаем необходимые компоненты в класс во время создания экземпляра.
Для начала нам нужно импортировать зависимость spring-context
в наш pom.xml
:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
Затем нам нужно настроить файл конфигурации .
Этот файл может быть файлом POJO или XML, в зависимости от предпочтений.
2. Конфигурация на основе аннотаций
Файлы конфигурации Java похожи на объекты Java с некоторыми дополнительными аннотациями:
@Configuration
@ComponentScan("com.foreach.constructordi")
public class Config {
@Bean
public Engine engine() {
return new Engine("v8", 5);
}
@Bean
public Transmission transmission() {
return new Transmission("sliding");
}
}
Здесь мы используем аннотации, чтобы уведомить среду выполнения Spring о том, что этот класс предоставляет определения bean-компонентов ( аннотация @Bean
) и что пакету com.foreach.spring
необходимо выполнить сканирование контекста для поиска дополнительных bean-компонентов. Далее мы определяем класс Car :
@Component
public class Car {
@Autowired
public Car(Engine engine, Transmission transmission) {
this.engine = engine;
this.transmission = transmission;
}
}
Spring встретится с нашим классом Car
при сканировании пакета и инициализирует его экземпляр, вызвав аннотированный конструктор @Autowired .
Вызывая аннотированные методы @Bean класса
Config
, мы получим экземпляры Engine и Transmission
. Наконец, нам нужно загрузить ApplicationContext
, используя нашу конфигурацию POJO:
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Car car = context.getBean(Car.class);
3. Неявное внедрение конструктора
Начиная с Spring 4.3, классы с одним конструктором могут опускать аннотацию @Autowired
. Это приятное небольшое удобство и удаление шаблонов.
Кроме того, начиная с версии 4.3, мы можем использовать внедрение на основе конструктора в аннотированные классы @Configuration .
Кроме того, если такой класс имеет только один конструктор, мы также можем опустить аннотацию @Autowired .
4. Конфигурация на основе XML
Другой способ настроить среду выполнения Spring с внедрением зависимостей на основе конструктора — использовать файл конфигурации XML:
<bean id="toyota" class="com.foreach.constructordi.domain.Car">
<constructor-arg index="0" ref="engine"/>
<constructor-arg index="1" ref="transmission"/>
</bean>
<bean id="engine" class="com.foreach.constructordi.domain.Engine">
<constructor-arg index="0" value="v4"/>
<constructor-arg index="1" value="2"/>
</bean>
<bean id="transmission" class="com.foreach.constructordi.domain.Transmission">
<constructor-arg value="sliding"/>
</bean>
Обратите внимание, что аргумент-конструктор
может принимать буквальное значение или ссылку на другой компонент, и что может быть предоставлен необязательный явный индекс
и тип .
Мы можем использовать атрибуты Type
и index
для устранения неоднозначности (например, если конструктор принимает несколько аргументов одного типа).
Атрибут имени
также можно использовать для сопоставления переменных xml и java, но тогда ваш коддолжен
быть скомпилирован с включенным флагом отладки.
В этом случае нам нужно загрузить наш контекст приложения Spring, используя ClassPathXmlApplicationContext
:
ApplicationContext context = new ClassPathXmlApplicationContext("foreach.xml");
Car car = context.getBean(Car.class);
5. Плюсы и минусы
Внедрение конструктора имеет несколько преимуществ по сравнению с внедрением в поле.
Первое преимущество — тестируемость. Предположим, мы собираемся протестировать bean-компонент Spring, который использует внедрение полей:
public class UserService {
@Autowired
private UserRepository userRepository;
}
Во время создания экземпляра UserService
мы не можем инициализировать состояние userRepository
. Единственный способ добиться этого — через Reflection API , который полностью нарушает инкапсуляцию. Кроме того, полученный код будет менее безопасным по сравнению с простым вызовом конструктора.
Кроме того, с внедрением полей мы не можем применять инварианты на уровне класса, поэтому можно иметь экземпляр UserService
без
должным образом инициализированного userRepository
. Поэтому мы можем столкнуться со случайными исключениями NullPointerException
тут и там. Кроме того, с внедрением конструктора проще создавать неизменяемые компоненты.
``
Более того, использование конструкторов для создания экземпляров объектов более естественно с точки зрения ООП.
С другой стороны, основным недостатком внедрения конструктора является его многословность, особенно когда компонент имеет несколько зависимостей. Иногда это может быть замаскированным благословением, поскольку мы можем изо всех сил стараться свести количество зависимостей к минимуму.
6. Заключение
В этой краткой статье были продемонстрированы основы двух различных способов использования внедрения зависимостей на основе конструктора
с использованием среды Spring.
Полную реализацию этой статьи можно найти на GitHub .