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

Основные аннотации Spring

October 26, 2023 . 9 минут на чтение статьи

Основное преимущество Spring в том, что он умеет делать магию, и магия эта практически полностью основана на аннотациях. Иначе говоря, в Spring уже решено большое количество типичных проблем, и чтобы активировать решение какой-то проблемы, достаточно написать над классом @РешениеМоейПроблемы.

Основные аннотации Cпринга нужны для удовлетворения базовых потребностей человека, создающего каркас приложения. Вам хочется, чтобы привычные архитектурные паттерны (типа MVC или IoC) включались парой строчек кода. Об этом и поговорим.

Инъекция зависимостей

"Инъекция зависимостей" - убогая калька с английского "Dependency Injection". Почему убогая? Потому что в русском языке это слово "инъекция" произносить неудобно, особенно в пьяном виде. Если вы хотите понять, насколько ваша жена прибухнула сегодня, поговорите с ней об инъекции зависимостей.

Но если она использует Spring, то сможет уйти от ответственности. Всё, что связано с подстановкой компонентов делается здесь максимально просто. (Иногда оно ломается из-за бага в Spring, и тогда ты попадаешь в ад. Но вначале ты кайфуешь, по сравнению с другими технологиями).

@Autowired

Аннотация @Autowired — команда для Spring начать поиск и подстановку зависимости. После того, как кандидат найдет, существует несколько способов выполнить подстановку.

Давайте засунем пилота в самолёт.

Инъекция через конструктор (constructor injection):

class Plane {
    Pilot pilot;

    @Autowired
    Plane(Pilot pilot) {
        this.pilot = pilot;
    }
}

Инъекция через сеттер (setter injection):

class Plane {
    Pilot pilot;

    @Autowired
    void setPilot(Pilot pilot) {
        this.pilot = pilot;
    }
}

Инъекция через поле (field injection):

class Plane {
    @Autowired
    Pilot pilot;
}

Если мы используем для инъекции конструктор, то все аргументы конструктора - обязательны. Нельзя что-то пропустить и надеяться, что Spring сам сочинит за тебя код.

В некоторых задачах он так делает, и это боль. В данном случае, создатели Spring решили не экспериментировать, потому что это самая важная механика, и если она сломалась бы, отладить такое автоматически написанное приложение было бы невозможно. Это было бы всё равно что пытаться починить код, написанный нейросетью.

Внедрение через конструктор интересно тем, что оно считается "способом по-умолчанию". Когда-то давно, тебе нужно было указывать аннотацию @Autowired над каждым конструктором. Сейчас это нужно делать, только если в классе несколько конструкторов (Spring не может прочитать твои мысли и угадать, какой тебе нравится больше).

У аннотации Autowired есть аргумент reqiured, с помощью него можно сказать, что если кандидат не найден, то это фатальная ошибка. В обычном коде с динамическим поиском несуществующих зависимостей лучше не экспериментировать, иначе Java быстро превратится в C++ со SFINAE, нам этого не нужно.

Что почитать?

@Bean

Аннотация @Bean используется, чтобы пометить метод, с помощью которого создается новый класс. Именно этот класс мы и будем подставлять. Это более всего напоминает архитектурный паттерн Factory Method.

@Bean
Pilot pilot() {
    return new Pilot();
}

По сути, эта пометка является своеобразной конфигурацией приложения, поэтому помечать ей можно только методы, находящиеся внутри @Configuration (класса, который так аннотирован).

В приложении может быть много компонентов, сделанных из одного класса. Чтобы их как-то различать, можно аргументом передать кастомное имя конкретного этого компонента. По-умолчанию имя берется из названия метода (то есть, если сделать метод getVodka, то бин будет называться vodka).

@Bean("pilot")
Pilot getPilot() {
    return new Pilot();
}

Что почитать?

@Qualifier

Квалификаторы используются, когда Spring не может телепатически понять, какой класс внутри сложной иерархии использовать для подстановки.

Представь, что ты Илон Маск и выбираешь, лететь ли домой на истребителе или на кукурузнике.

class FighterPlane implements Plane {}

class CivilPlane implements Plane {}

Spring ничего не может посоветовать, потому что Тесла не производит ни одно из этих средств передвижения. Придется выбирать самостоятельно! Как всегда, это делается кучей способов.

Инъекция в конструктор (constructor injection):

@Autowired
ElonMusk(@Qualifier("figherplane") Plane plane) {
    this.plane = plane;
}

Инъекция в сеттер (setter injection):

@Autowired
void setPlane(@Qualifier("figherplane") Plane plane) {
    this.plane = plane;
}

Можно сделать и по-другому:

@Autowired
@Qualifier("figherplane")
void setPlane(Plane plane) {
    this.plane = plane;
}

Инъекция в поле (field injection):

@Autowired
@Qualifier("figherplane")
Plane plane;

Что почитать?

@Required

Если вы используете XML и хотите отметить обязательность какого-то поля, можно сделать это прямо внутри Java-кода.

Обязательность:

@Required
void setColor(String color) {
    this.color = color;
}

Мы будем настраивать вот этот XML:

<bean class="oleg.guru.annotations.Plane">
    <property name="color" value="green" />
</bean>

Напоминаю, что в 2023+ году вы не обязаны что-то настраивать в XML. Но если вас накормили куском легаси говна, делать так придется часто.

@Value

Аннотация @Value нужна, чтобы подставлять динамические значения.

Представим, что у нас в application.properties, или где-то еще в файлах настроек, есть строка plane.wingsCount=2. Мы сделали ее в расчете на далекое будущее, когда Илон Маск придумает самолеты с четырьмя крыльями, как в Звездных Войнах. Через тысячу лет у нас даже исходников этого приложения не останется, а вот в properties-файле поправить значение будет легко.

Если вы знаете, что такое SpEL, то использовать такую подстановку можно даже там. Это важное замечание потому, что фичи, которые казалось бы дополняют друг друга, делают это далеко не всегда. Гораздо чаще они конфликтуют между собой.

Constructor injection:

Plane(@Value("${plane.wingsCount}") int wingsCount) {
    this.wingsCount = wingsCount;
}

Setter injection:

@Autowired
void setWingsCount(@Value("${plane.wingsCount}") int wingsCount) {
    this.wingsCount = wingsCount;
}

Как всегда, альтернативный способ:

@Value("${plane.wingsCount}")
void setWingsCount(int wingsCount) {
    this.wingsCount = wingsCount;
}

Field injection:

@Value("${plane.wingsCount}")
int wingsCount;

Что почитать?

@DependsOn

Компоненты не существуют в вакууме. Чтобы собрать самолет, нужны крылья, шасси и голова. Все запчасти нужно произвести до того, как мы начнем собирать самолет.

Отношение зависимости между частями выражается аннотацией @DependsOn. Это необычная аннотация, потому что если ты инжектишь зависимости внутрь класса явно, использовать ее никогда не нужно. Примеры явного использования были выше. Эта аннотация отвечает только за неявное использование, например при внедрении настроек JDBC-драйвера через Spring.

@DependsOn("wings")
class FigherPlane implements Plane {}
@Bean
@DependsOn("wings")
Plane plane() {
    return new Plane();
}

Помните: явное лучше неявного. Несмотря на то, что Spring демонстрирует обратное поведение (мы не видим кода, обрабатывающего аннотации), надо понимать, что Spring на протяжении десятков лет пишут лучше в мире инженеры. Твой быдлокод, написанный за вечер на коленке, совершенно не таков. Поэтому, используй обычный @Autowired и не выпендривайся.

Что почитать?

  • Официальная документация @DependsOn

@Lazy

По-умолчанию, Spring создает бины проактивно. Когда мы строим самолет, крылья и шасси должны быть готовы. Тем не менее, если вы захотите в самолете пожрать, не стоит разогревать шашлык заранее, еще на земле. Когда полетите, он уже остынет.

В зависимости от того, на какой элемент (т.е. вместе с какими аннотациями) вы употребляете @Lazy, результат будет разный.

  • Навесив его на фактори-метод @Bean, вы отложите вызов метода, т.е. создание бина.
  • Если навесить его выше, на всю @Configuration, то это скажется на всех бинах внутри конфигурации.
  • Класс, помеченный как @Component, но не находящийся в конфигурации, будет инициализироваться лениво сам по себе.
  • Если мы помечаем места инъекции (помеченные аннотацией @Autowired на конструкторе, сеттере или поле), то создастся временный динамический прокси. Такая зависимость будет инициализирована лениво, даже если изначально она этого делать не планировала.

Ленивость можно включать и выключать: у @Lazy есть параметр value, по-умолчанию равный true. Давайте будем готовить еду уже в полёте:

@Configuration
@Lazy
class PlaneConfig {

    @Bean
    @Lazy(false)
    Food food() {
        return new Food();
    }
}

Что почитать?

  • Официальная документация @Lazy

@Lookup

Аннотация @Lookup нужна, чтобы переопределить проаннотированный метод. "ООП в стиле Spring", добро пожаловать в Ад. Аргументы и тип возвращаемого значения будут использоваться для BeanFactory#getBean.

Эта аннотация требует дополнительного объяснения большим количеством кода. Когда-нибудь я про неё напишу отдельно.

Что почитать?

  • Официальная документация @Lookup

@Primary

Иногда у нас есть много бинов одного типа, а квалификаторов (@Qualifier) не завезли. В примере выше, Илон Маск скорее поедет на автомобиле Тесла, чем полетит на истребителе производства Lockheed Martin.

@Component
@Primary
class TeslaCar implements Vehicle  {}

@Component
class FigherJet implements Plane {}

Когда-нибудь Маск купит Lockheed Martin с потрохами, но на этот случай у нас есть другие аннотации Spring.

Что почитать?

  • Официальная документация @Primary

@Scope

Аннотация @Scope управляет созданием @Component и @Bean. Среди возможных опций - singleton, prototype, request, session, globalSession. Про это лучше прочитать в документации.

Что почитать?



Не забывайте подписаться на наши ресурсы, там есть ништяки:

  • CodCraft - Youtube-канал от автора этого гайда
  • Оправдания от Олега - Telegram-чат автора (общий, про всё на свете)
  • Javawatch - Telegram-канал про Java
  • Telegram-канал Failover Bar - единственный в Санкт-Петербурге (а может, и в России вообще) бар для разработчиков. Мы здесь постоянно встречаемся и разговариваем про Java.

Настройка контекста Spring

Контекст — это некоторое окружение, в котором работает приложение на Spring Framework. Его можно настраивать так же, с помощью набора аннотаций.

@Profile

Иногда нам хочется иметь разные наборы настроек. Настройки отличаются для тестового окруения и для продакшена, для облака и для собственного железа, и так далее.

Представим, что у Илона Маска есть режим лицемерия, когда он подлизывается к ФБР США, и всё-таки согласен лететь на самолёте от Lockheed Martin.

@Component
@Profile("лицемерие")
class FigherJet implements Vehicle {}

@Import

По-умолчанию подразумевается, что Spring ищет ваши компоненты в каких-то заранее заданных пакетах. Он ищет там классы @Configuration и инициализирует их.

Но вы можете использовать @Configuration и без необходимости сканирования кучи ненужных директорий. Достаточно импортировать их вручную.

@Import(LockheedMartin.class)
class PlaneFactoryConfig {}

@ImportResource

С помощью этой аннотации вы можете импортировать XML-конфигурации. Это всё ещё бывает полезно, потому что иногда приложение настраивает не программист, а сисадмин. Обычно это заканчивается печально, но если таково желание заказчика, пусть будет так.

@Configuration
@ImportResource("classpath:/LockheedMartin.xml")
class PlaneFactoryConfig {}

@PropertySource

Гораздо разумней хранить настройки в обычных propleries-файлах, и угадайте что, для этого тоже есть аннотация.

Вы можете указывать эту аннотацию много раз, поэтому в одном-единственном классе можно собрать вообще все свои настройки. В старых версиях Java для этого использовалась аннотация PropertySources, поэтому не пугайтесь, если встретите ее в каком-то дремучем легаси.

@Configuration
@PropertySource("classpath:/LockheedMartin.properties")
@PropertySource("classpath:/Tesla.properties")
class PlaneFactoryConfig {}

Profit

В этой статье вы познакомились с основными аннотациями Spring. Через них можно настроить всё что угодно.

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

Сейчас стрелочка повернулась. Если вы используете Spring и Spring Boot, то иногда получается приложение, чуть менее чем полностью состоящее из аннотаций и пустых методов. Как они взаимодействуют между собой - совершенно не понять. Это принесло Spring новую дурную славу.

Но кажется, как в меме про осла, если ты делаешь что-то хорошее, у тебя всегда есть проблемы. А между тем, Spring - лучший фреймворк для создания микросервисов, поэтому - какие у вас вообще есть варианты? Учите аннотации, и да пребудет с вами Сила.

Не забывайте подписаться на наши ресурсы, там есть ништяки:

  • CodCraft - Youtube-канал от автора этого гайда
  • Оправдания от Олега - Telegram-чат автора (общий, про всё на свете)
  • Javawatch - Telegram-канал про Java
  • Telegram-канал Failover Bar - единственный в Санкт-Петербурге (а может, и в России вообще) бар для разработчиков. Мы здесь постоянно встречаемся и разговариваем про Java.