Аннотация @Autowired в Spring
October 29, 2023 . 6 минут на чтение статьиОснова Spring - механизм Dependency Inection. Он работает так: во все поля, которые отмечены аннотацией @Autowired
, автоматически подставляются заранее заданные значения.
Это позволяет развязать компоненты приложения между собой: в момент запуска приложения, связывать их будет вместо вас Spring.
В этой статье мы посмотрим, как использовать аннотацию @Autowired
и разрешать конфликты подстановки с помощью @Qualifier
.
Как включить @Autowired
Настройка Spring делается с помощью конфигурационных классов, помеченных аннотацией @Configuration
. Чтобы не перечислять вручную все бины в мире, можно попросить Spring найти подходящие компоненты автоматически: это делает аннотация @ComponentScan
.
@Configuration
@ComponentScan("guru.oleg")
public class Config {}
XML-некрофилы могут сделать то же самое с помощью <context:annotation-config>
.
@ComponentScan
не обязательно включать, если вам достаточно значений по-умолчанию. Сканирование уже включено в аннотацию @SpringBootApplication
, которую использует тот же Spring Initializr. Эта аннотация - полный экивалент комбинации @Configuration
+ @EnableAutoConfiguration
+ @ComponentScan
, с настройками по-умолчанию. Поиск компонентов запустится на текущем пакете (рекурсивно, вместе со всем содержимым).
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}}
Как использовать @Autowired
@Autowired
можно навесить на несколько элементов: поля, сеттеры и конструкторы. Эти способы отличаются по визуальному стилю и удобству использования в разных кейсах.
@Autowired на полях класса
Вместо тысячи слов, смотрим на пример. Вначале, создадим компонент:
@Component("printer")
public class Printer {
public String print() {
return "printed data";
}
}
Цель нашей спецоперации в том, чтобы подставить теперь этот компонент в поля какого-нибудь другого класса. И чтобы это произошло автоматически, без нашего участия.
@Component
public class PrintService {
@Autowired
private Printer printer;
}
Таким образом, мы можем конструировать иерархии компонентов, не особо заморачиваясь, какие конструкторы, с какими параметрами, и в каком порядке надо вызывать. Удобно.
Этот способ принято критиковать потому, что его сложно автоматически тестировать. Чтобы подставить пробные данные в автотесте, приходится использовать Reflection API. Такой подход многие считают костылем. Мне, напротив, кажется, что игра стоит свеч. Впрочем, это интересный архитектурный вопрос и решение, которое вам нужно принять самостоятельно, а не слушать советов из нижнего интернета.
@Autowired на сеттерах
Промежуточный способ между внедрением через поля и через конструкторы. Уже можно тестировать без костылей, но всё ещё некрасиво.
Хот тейк: сеттеры в Java вообще не нужны. Их придумали поборники "правильного ООП", а такое ООП в большинстве случаев не нужно. Гораздо лучше все поля делать публичными, а их доступность или недоступность определять по смыслу (как это делается в том же Python). Тем не менее, невозможно не привести этот способ для полноты картины.
public class PrintService {
private Printer printer;
@Autowired
public void setPrinter(Printer printer) {
this.printer = printer;
}
}
@Autowired на конструкторах
Этот способ считается основным и каноничным. Его одновременно можно и тестировать без костылей, и он передает семантику происходящего процесса, и он нормально работает с публичными полями.
public class PrintService {
private Printer printer;
@Autowired
public void PrintService(Printer printer) {
this.printer = printer;
}
}
Проблема с этим способом мне видится в том, что объявление полей и аннотации находятся в совершенно разных местах кода. Поэтому, вам недостаточно просто посмотреть на поля, а нужно идти куда-то в дебри кода и разбираться, что куда инжектится. Это немного нарушает общую идею красоты и простоты, которую дает Spring. Что важнее - идеологическая правильность или красота и юзабилити, решать вам.
@Autowired и необязательные зависимости
По-умолчанию, все зависимости обязательные. Если запустить приложение, не предоставив чего-то важного, оно упадет с такой ошибкой:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [guru.oleg.dto] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations:
{@org.springframework.beans.factory.annotation.Autowired(required=true)}
Если же вы осознанно хотите использовать опциональные зависимости, то нужно выставить значение required=false
:
public class PrintService {
@Autowired(required = false)
private Printer printer;
}
Разрешение конфликтов
По-умолчанию, Spring считает источником истины типы зависимостей. В своем внутреннем реестре, он приводит названия типов к нижнему регистру и использует эти идентификаторы для поиска соответствий.
Что произойдет, если у нас найдутся несколько компонентов с одинаково называющимся классом? Будет ошибка такого вида:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [guru.oleg.Printer] is defined:
expected single matching bean but found 2: A0Printer,A4Printer
Как может возникнуть такая ситуация? Представим, что у нас есть два принтера: один для маленьких листочков формата A4, и другой - для широкоформатной печати на A0.
@Component("A0Printer")
public class A0Printer implements Printer {
public String print() {
return "A0 paper";
}
}
@Component("A4Printer")
public class A4Printer implements Printer {
public String format() {
return "A4 paper";
}
}
Теперь, если мы попробуем действовать в парадигме ООП, и попросим Spring найти нам "какой-нибудь принтер", то ему станет плохо, вылетит ошибка NoUniqueBeanDefinitionException
.
public class PrintService {
@Autowired
private Printer printer;
}
Помните, мы обсуждали, что по-умолчанию Spring использует имена, приеденные к нижнему регистру? Мы можем заставить Spring искать не какой-то непонятный принтер, а имя нашего класса в нижнем регистре. Для этого служит аннотация @Qualifier
.
public class PrintService {
@Autowired
@Qualifier("A4Printer")
private Printer printer;
}
Собственный квалификатор
Если простой квалификатор кажется вам зашкваром, то можно использовать кастомный, красивый квалификатор с осмысленно выглядящим типом.
@Qualifier
@Target({
ElementType.FIELD,
ElementType.METHOD,
ElementType.TYPE,
ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrinterType {
String value();
}
Описание компонентов начинают выглядеть немного по-другому:
@PrinterType("A0Printer")
@Component
public class A0Printer implements Printer {
public String print() {
return "A0 paper";
}
}
@PrinterType("A4Printer")
@Component
public class A4Printer implements Printer {
public String format() {
return "A4 paper";
}
}
Теперь, воспользуемся плодами нашего труда:
public class PrintService {
@Autowired
@PrinterType("A4Printer")
private Printer printer;
}
Такая запись выглядит более семантично. Так в бизнес-логику утекает на одну подробность работы Спринга меньше. На самом деле, видеть повсюду торчащие куски Спринга (типа аннотации @Qualifier
) - последнее, что вам хочется видеть в вашем красивом, ухоженном коде.
Внедрение зависимости по имени
Если вы внедряете зависимости в поля класса, то Spring включает удобную эвристику: он может получить имя класса прямо из имени поля.
public class PrintService {
@Autowired
private Printer a4Printer;
}
Этот пример принципиально отличается от предыдущих. Раньше мы использовали полиморфизм: мы указывали общий интерфейс принтера в надежде, что Spring найдет нам подходящий класс. Здесь же мы захардкодили имя класса прямо в название поля.
Оба подхода имеют право на жизнь. Первый подход полезен, если нужно внедрять имена компонентов через плейсхолдеры из файлов конфигурации. Второй подход с прямым указанием имени больше подходит для создания псевдо-DSL в стиле Spring Data.
Выводы
В этой статье мы посмотрели, как в Spring можно связывать компоненты. У вас есть на выбор несколько мест, куда можно встроить свою зависимость. Плюс специальный инструментарий для разрешения конфликтов, если эти конфликты вообще произойдут.
Продолжайте читать наши гайды по Spring и станете круче самых крутых гор!
Что почитать?
- Официальный гайд по аннотации @Autowired
- Официальный гайд по способам внедрения зависимостей
- Официальная документация на аннотацию @Autowired
- Официальная документация на аннотацию @Qualifier
Не забывайте подписаться на наши ресурсы, там есть ништяки:
- CodCraft - Youtube-канал от автора этого гайда
- Оправдания от Олега - Telegram-чат автора (общий, про всё на свете)
- Javawatch - Telegram-канал про Java
- Telegram-канал Failover Bar - единственный в Санкт-Петербурге (а может, и в России вообще) бар для разработчиков. Мы здесь постоянно встречаемся и разговариваем про Java.