Блог создается при поддержке Axiom JDK и Failover Bar

Внедрение зависимостей через конструктор в 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.