Spring Framework и инициализация компонентов

Создание компонентов

Основными элементами в контексте приложения, использующего Spring Framework, являются компоненты или бины (beans), используемые для внедрения зависимостей (Dependency Injection, DI). Есть два основных способа создания таких компонентов.

Создание компонента при помощи аннотаций

Самый простой и наиболее распространённый способ создания компонента — при помощи аннотации @Component или аннотаций, наследующих её, таких как @Service, @Repository или @Controller из пакета org.springframework.stereotype.

Пример простого компонента:

В данном случае будет создан компонент типа SimpleBean с именем и идентификатором simpleBean. Контекст Spring автоматически найдёт этот класс и создаст объект этого типа, который будет использоваться в дальнейшем для внедрения зависимостей.

Создание компонента при помощи @Bean-метода

Ещё один популярный способ создания компонентов — при помощи метода, помеченного аннотацией @Bean. В этом случае сам тип компонента не нужно помечать аннотацией @Component.

В данном случае будут созданы 2 компонента типа SimpleBean и с именами и идентификаторами simpleBean и anotherSimpleBean. Данный способ удобен тем, что можно создавать компоненты сторонних классов. Так же отмечу тот момент, что создание компонентов при помощи @Bean-методов возможно только в классах, помеченных аннотацией @Confuguration, как это показано в примере выше. Так же этот код соответствует следующей XML-конфигурации:

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

Использование компонентов

Теперь, когда мы разобрались с созданием компонентов, можно приступать к рассмотрению примеров их использования. Компоненты можно внедрять в другие компоненты, и самый распространённый пример — внедрение репозитория в контроллер. Внедрять зависимости можно тоже несколькими способами.

Внедрение через свойство класса

Самый распространённый, но не самый удобный для тестирования, способ внедрения, о чём я как-нибудь напишу в заметке о тестировании.

В данном случае контекст Spring находит класс, помеченный аннотацией @Controller, создаёт объект этого класса, находит свойство помеченное аннотацией @Autowired и присваивает указанному свойству компонент нужного класса, находящийся в контексте Spring.

Тут может произойти две ошибки: в контексте Spring не окажется компонента класса SimpleBean, либо в контексте окажется более одного компонента класса SimpleBean. Об этом я расскажу чуть ниже.

Внедрение через конструктор

Внедрение через конструктор значительно удобнее, так как при тестировании даёт возможность вручную создать объект класса и передать ему мок вместо реального компонента.

Данный код соответствует XML-конфигурации:

Так же внедряемое свойство можно сделать final, как это показано в примере. Стоит помнить, что аннотация @Autowired ставится на метод, а не на аргумент. Фактически это даёт возможность передавать несколько внедряемых компонентов в конструктор, что и показано в примере.

Внедрение через сеттер

Внедрение через сеттер аналогично внедрению через конструктор с той разницей, что свойство класса нельзя сделать final.

Данный код соответствует следующей XML-кофигурации:

Подводные камни

Процесс внедрения компонентов

Процесс подбора кандидата на внедрение следующий:

  1. Контекст пытается найти компонент указанного типа и с указанным именем или идентификатором (по умолчанию в качестве имени и идентификатора используется имя внедряемого компонента). Например:

    В данном случае контекст попытается найти компонент типа SimpleBean с идентификатором/именем someSimpleBean
  2. Если первый способ не увенчался успехом, контекст попытается найти любой компонент типа SimpleBean. Если компонентов этого типа в контексте больше одного, то попытка инициализации завершится выкинутым исключением.
  3. Если и второй шаг не дал результата, то будет выкинуто исключение об отсутствии кандидата на внедрение.

Поиск компонентов

По умолчанию контекст Spring ищет компоненты, находящиеся в пакетах, нижестоящих относительно класса с аннотацией @ComponentScan. Иными словами, если у нас в пакете name.alexkosarev.blog есть класс, помеченный @ComponentScan, то поиск компонентов будет вестись в пакетах name.alexkosarev.blog, name.alexkosarev.blog.beans, name.alexkosarev.blog.controllers и т.д., но компоненты, лежащие в пакете name.alexkosarev не будут найдены. Что бы включить в контекст компоненты из вышестоящих пакетов, нужно в аннотацию @ComponentScan передать аргумент basePackages с пакетом, относительно которого нужно искать компоненты:

Контекст не может найти кандидата на внедрение

Пожалуй, самая распространённая ошибка. Заключается она в том, что внедряемый компонент не был создан, либо был создан вручную, а не через контекст Spring. При возникновении такой ошибки нужно убедиться, что метод, создающий внедряемый компонент помечен аннотацией @Bean, или у класса компонента есть соответствующая аннотация, и сам класс находится в достижимом для поиска компонентов пакете.

Контекст не смог внедрить зависимость из-за того, что кандидатов более одного

Ещё одна очень распространённая ошибка. Взгляните на пример создания компонентов при помощи @Bean-методов. В этом случае у нас будет два компонента типа SimpleBean с идентификаторами simpleBean и anotherSimpleBean, в следствие чего следующий код приведёт именно к этой ошибке:

Всё потому что в контексте нет компонента с именем someSimpleBean, зато есть два компонента типа SimpleBean. Исправить эту ошибку можно двумя способами:

  • Указать правильное имя, заменив someSimpleBean на simpleBean или anotherSimpleBean
  • Используя @Qualifier с указанием конкретного существующего имени компонента:

     

Это, пожалуй, всё, что нужно знать и помнить о работе компонентов в Spring Framework.

Полезные ссылки

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