Внедрение зависимостей через конструктор в Spring
October 29, 2023 . 4 минут на чтение статьиВся магия Spring строится поверх внедрения зависимостей (Dependency Injection, DI). Вы можете поделить свой код на модули, и парой строчек склеивать эти модули вместе. В момент подключения зависимости, ее можно обработать самыми разными способами — например, динамически вычислить поля, или сходить в базу данных и заполнить их актуальными данными. Благодаря простоте и удобству этой системы Spring стал самым популярным фреймворком в Java-мире.
В этом гайде мы поговорим о том, как внедрять зависимости, указывая связь между модулями в конструкторе.
Чтобы эта система заработала, в зависимостях проекта (в Maven или Gradle) нужно указать spring-context
:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.13</version>
</dependency>
Дальше, нам нужна конфигурация Spring. Сейчас это делается через аннотацию @Configuration
, но когда-то в далеком прошлом люди делали это через XML.
Настройка через аннотации
Чтобы настроить Spring-приложение, нужно пометить класс аннотацией @Configuration
. Внутри указать все используемые бины, пометив их аннотацией @Bean
.
@Configuration
@ComponentScan("guru.oleg")
public class Config {
@Bean
public Chassis chassis() {
return new Chassis();
}
}
Теперь, создадим новый компонент, и положим их куда-нибудь внутрь пакета guru.oleg
. Мы заранее указали, что именно там нужно искать компоненты, с помощью аннотации @ComponentScan
.
@Component
public class Jet {
@Autowired
public Jet(Chassis chassis) {
this.chassis = chassis;
}
}
Когда Spring находит компонент, он сразу ищет конструктор, помеченный аннотацией @Autowired
.
Дальше он находит в конфигурации Bean-методы с подходящим типом, запускает их, и заполняет полученными значениями аргументы конструктора.
Чтобы весь этот процесс запустился, нужно в каком-то месте приложения (обычно, в main-классе) создать контекст Спринга.
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Jet jet = context.getBean(Jet.class);
Это супер простая система. Надежная, как автомат Калашникова. Любой Spring-разработчик умеет собирать и разбирать приложение из файла конфигурации, бинов и компонентов.
Из интересных особенностей: если у бина всего один конструктор, то использовать аннотацию @Autowired
не обязательно. Spring и так понимает, что вариантов у него немного. Если же есть какая-то неоднозначность, приложение упадет с ошибкой, и вам нужно будет проставить @Autowired
на тех конструкторах, которые вам больше по душе.
Настройка с помощью XML
Настраивать Spring можно по-старинке, с помощью XML. Обычно так делают только XML-некрофилы и сисадмины.
У сисадминов есть очень конкретная проблема: пересобирать леагаси-приложение с кодом, написанным много лет назад — безумие. Хорошо написанное приложение обычно можно настроить через properties-файлы или через базу данных, но не все программы написаны хорошо. Так что, если ты видишь человека, который настраивает Spring через XML, не спеши его унижать — возможно, это сисадмин, а само приложение — кусок говна.
Вот пример настройки через XML:
<bean id="ILSturmovik" class="guru.oleg.Jet">
<constructor-arg index="0" ref="chassis"/>
</bean>
<bean id="chassis" class="guru.oleg.Chassis"/>
В тэге constructor-arg
можно передать либо напрямую значение через value
, либо указать ссылку на какой-нибудь другой бин. В момент подстановки могут возникнуть неоднозначности. Чтобы с ними разобраться есть необязательные параметры index
и type
.
Самое загадочное свойство у аргументов - это name
. Оно тоже можно использовать для разрешения неоднозначности, но сработает этот флаг только в отладочном режиме.
Чтобы настроить Spring через XML, вам нужно создать контекст из этого XML:
ApplicationContext context = new ClassPathXmlApplicationContext("oleg.guru.xml");
Jet jet = context.getBean(Jet.class);
Через конструктор или через поле?
Обычно считается, что внедрение зависимостей через конструктор — это самый правильный способ. Тем не менее, может ли миллион мух ошибаться?
Самый главный аргумент пользу этого способа — удобство автоматического тестирования. Если вы пишите автотест на внедрение через поля, и все эти поля — приватные, то открывать эти поля придется через Reflection API. Это очень неприятный костыль, который делает больно любителям красивого ООП и надежной инкапсуляции. Если же вы внедряете зависимости через конструтор, то компоненты достаточно передать как аргументы этого конструктора, и никаких костылей не нужно.
Лично мой главный аргумент против (я использую внедрение через поля) в том, что это выглядит некрасиво и неинтуитивно. У тебя есть список полей, но что в них вставляется — нужно искать где-то в дребрях кода. А еще, вы всегда можете сделать поля класса публичными. Это совершенно не походит на правильное ООП, но очень удобно на практике.
Заключение
В этой короткой статье мы посмотрели на способы внедрения зависимостей через конструктор: с помощью аннотаций, и с помощью XML. Вы еще не раз встретите информацию о способах внедрения зависимостей в нашей серии гайдов по Spring.
Что почитать?
- Официальный гайд по способам внедрения зависимостей
Не забывайте подписаться на наши ресурсы, там есть ништяки:
- CodCraft - Youtube-канал от автора этого гайда
- Оправдания от Олега - Telegram-чат автора (общий, про всё на свете)
- Javawatch - Telegram-канал про Java
- Telegram-канал Failover Bar - единственный в Санкт-Петербурге (а может, и в России вообще) бар для разработчиков. Мы здесь постоянно встречаемся и разговариваем про Java.