Создание компонентов
Основными элементами в контексте приложения, использующего Spring Framework, являются компоненты или бины (beans), используемые для внедрения зависимостей (Dependency Injection, DI). Есть два основных способа создания таких компонентов.
Создание компонента при помощи аннотаций
Самый простой и наиболее распространённый способ создания компонента — при помощи аннотации @Component или аннотаций, наследующих её, таких как @Service, @Repository или @Controller из пакета org.springframework.stereotype.
Пример простого компонента:
1 2 3 4 5 6 7 8 9 |
import org.springframework.stereotype.Component; @Component public class SimpleBean { public String hello() { return "Hello world!"; } } |
В данном случае будет создан компонент типа SimpleBean с именем и идентификатором simpleBean. Контекст Spring автоматически найдёт этот класс и создаст объект этого типа, который будет использоваться в дальнейшем для внедрения зависимостей.
Создание компонента при помощи @Bean-метода
Ещё один популярный способ создания компонентов — при помощи метода, помеченного аннотацией @Bean. В этом случае сам тип компонента не нужно помечать аннотацией @Component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import org.acruxsource.sandbox.filters.SimpleBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ContextConfig { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } @Bean public SimpleBean anotherSimpleBean() { return new SimpleBean(); } } |
В данном случае будут созданы 2 компонента типа SimpleBean и с именами и идентификаторами simpleBean и anotherSimpleBean. Данный способ удобен тем, что можно создавать компоненты сторонних классов. Так же отмечу тот момент, что создание компонентов при помощи @Bean-методов возможно только в классах, помеченных аннотацией @Confuguration, как это показано в примере выше. Так же этот код соответствует следующей XML-конфигурации:
1 2 |
<bean class="name.alexkosarev.blog.beans.SimpleBean" name="simpleBean"/> <bean class="name.alexkosarev.blog.beans.SimpleBean" name="anotherSimpleBean"/> |
При создании компонентов таким способом стоит помнить, что если в классе, объект которого вы создаёте, есть зависимости внедряемые при помощи аннотации @Autowired, они будут автоматически внедрены.
Использование компонентов
Теперь, когда мы разобрались с созданием компонентов, можно приступать к рассмотрению примеров их использования. Компоненты можно внедрять в другие компоненты, и самый распространённый пример — внедрение репозитория в контроллер. Внедрять зависимости можно тоже несколькими способами.
Внедрение через свойство класса
Самый распространённый, но не самый удобный для тестирования, способ внедрения, о чём я как-нибудь напишу в заметке о тестировании.
1 2 3 4 5 6 |
@Controller public class SimpleController { @Autowired private SimpleBean simpleBean; } |
В данном случае контекст Spring находит класс, помеченный аннотацией @Controller, создаёт объект этого класса, находит свойство помеченное аннотацией @Autowired и присваивает указанному свойству компонент нужного класса, находящийся в контексте Spring.
Тут может произойти две ошибки: в контексте Spring не окажется компонента класса SimpleBean, либо в контексте окажется более одного компонента класса SimpleBean. Об этом я расскажу чуть ниже.
Внедрение через конструктор
Внедрение через конструктор значительно удобнее, так как при тестировании даёт возможность вручную создать объект класса и передать ему мок вместо реального компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Controller class SimpleController { private final SimpleBean simpleBean; private final AnotherSimpleBean simpleBean; @Autowired public SimpleController(SimpleBean simpleBean, AnotherSimpleBean anotherSimpleBean) { this.simpleBean = simpleBean; this.anotherSimpleBean = anotherSimpleBean; } } |
Данный код соответствует XML-конфигурации:
1 2 3 4 5 |
<bean class="name.alexkosarev.blog.beans.controllers.SimpleController" c:simpleBean-ref="simpleBean" c:anotherSimpleBean-ref="anotherSimpleBean"/> <!-- или --> <bean class="name.alexkosarev.blog.beans.controllers.SimpleController" c:simpleBean="${simpleBean}" c:anotherSimpleBean="${anotherSimpleBean}"/> |
Так же внедряемое свойство можно сделать final, как это показано в примере. Стоит помнить, что аннотация @Autowired ставится на метод, а не на аргумент. Фактически это даёт возможность передавать несколько внедряемых компонентов в конструктор, что и показано в примере.
Внедрение через сеттер
Внедрение через сеттер аналогично внедрению через конструктор с той разницей, что свойство класса нельзя сделать final.
1 2 3 4 5 6 7 8 9 10 |
@Controller class SimpleController { private SimpleBean simpleBean; @Autowired public void setSimpleBean(SimpleBean simpleBean) { this.simpleBean = simpleBean; } } |
Данный код соответствует следующей XML-кофигурации:
1 2 3 4 5 |
<bean class="name.alexkosarev.blog.beans.controllers.SimpleController" p:simpleBean-ref="simpleBean"/> <!-- или --> <bean class="name.alexkosarev.blog.beans.controllers.SimpleController" p:simpleBean="${simpleBean}"/> |
Подводные камни
Процесс внедрения компонентов
Процесс подбора кандидата на внедрение следующий:
- Контекст пытается найти компонент указанного типа и с указанным именем или идентификатором (по умолчанию в качестве имени и идентификатора используется имя внедряемого компонента). Например:
12@Autowiredprivate SimpleBean someSimpleBean;
В данном случае контекст попытается найти компонент типа SimpleBean с идентификатором/именем someSimpleBean - Если первый способ не увенчался успехом, контекст попытается найти любой компонент типа SimpleBean. Если компонентов этого типа в контексте больше одного, то попытка инициализации завершится выкинутым исключением.
- Если и второй шаг не дал результата, то будет выкинуто исключение об отсутствии кандидата на внедрение.
Поиск компонентов
По умолчанию контекст Spring ищет компоненты, находящиеся в пакетах, нижестоящих относительно класса с аннотацией @ComponentScan. Иными словами, если у нас в пакете name.alexkosarev.blog есть класс, помеченный @ComponentScan, то поиск компонентов будет вестись в пакетах name.alexkosarev.blog, name.alexkosarev.blog.beans, name.alexkosarev.blog.controllers и т.д., но компоненты, лежащие в пакете name.alexkosarev не будут найдены. Что бы включить в контекст компоненты из вышестоящих пакетов, нужно в аннотацию @ComponentScan передать аргумент basePackages с пакетом, относительно которого нужно искать компоненты:
1 2 3 4 |
@Configuration @ComponentScan(basePackages = "name.alexkosarev") public class ApplicationConfig { } |
Контекст не может найти кандидата на внедрение
Пожалуй, самая распространённая ошибка. Заключается она в том, что внедряемый компонент не был создан, либо был создан вручную, а не через контекст Spring. При возникновении такой ошибки нужно убедиться, что метод, создающий внедряемый компонент помечен аннотацией @Bean, или у класса компонента есть соответствующая аннотация, и сам класс находится в достижимом для поиска компонентов пакете.
Контекст не смог внедрить зависимость из-за того, что кандидатов более одного
Ещё одна очень распространённая ошибка. Взгляните на пример создания компонентов при помощи @Bean-методов. В этом случае у нас будет два компонента типа SimpleBean с идентификаторами simpleBean и anotherSimpleBean, в следствие чего следующий код приведёт именно к этой ошибке:
1 2 |
@Autowired private SimpleBean someSimpleBean; |
Всё потому что в контексте нет компонента с именем someSimpleBean, зато есть два компонента типа SimpleBean. Исправить эту ошибку можно двумя способами:
- Указать правильное имя, заменив someSimpleBean на simpleBean или anotherSimpleBean
- Используя @Qualifier с указанием конкретного существующего имени компонента:
123@Autowired@Qualifier("anotherSimpleBean")private SimpleBean otherSimpleBean;
Это, пожалуй, всё, что нужно знать и помнить о работе компонентов в Spring Framework.
Полезные ссылки
- Официальная документация Spring Framework