Разница между <context:annotation-config> и <context:component-scan>
Я изучаю Spring 3
, и у меня возникли трудности с пониманием функциональности тегов <context:annotation-config>
и <context:component-scan>
.
Судя по тому, что я прочитал, эти теги обрабатывают разные аннотации (такие как @Required
, @Autowired
и т.д. против @Component
, @Repository
, @Service
и т.д.), но также, как я выяснил, они регистрируют одни и те же bean post processor классы.
Чтобы запутать меня еще больше, в <context:component-scan>
есть атрибут annotation-config
.
Может кто-то объяснить, в чем разница и сходство между этими тегами? Является ли один из них устаревшим по сравнению с другим, дополняют ли они друг друга, нужно ли мне использовать один из них или оба?
5 ответ(ов)
<context:annotation-config>
используется для активации аннотаций в бинах, которые уже зарегистрированы в контексте приложения (независимо от того, были ли они определены с помощью XML или найдены при сканировании пакетов).
<context:component-scan>
также выполняет функции, аналогичные <context:annotation-config>
, но дополнительно сканирует пакеты для поиска и регистрации бинов в контексте приложения.
Я приведу несколько примеров, чтобы показать различия и схожести.
Начнем с базовой настройки трех бинов типа A
, B
и C
, где B
и C
внедряются в A
.
package com.xxx;
public class B {
public B() {
System.out.println("создание бина B: " + this);
}
}
package com.xxx;
public class C {
public C() {
System.out.println("создание бина C: " + this);
}
}
package com.yyy;
import com.xxx.B;
import com.xxx.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("создание бина A: " + this);
}
public void setBbb(B bbb) {
System.out.println("установка A.bbb: " + bbb);
this.bbb = bbb;
}
public void setCcc(C ccc) {
System.out.println("установка A.ccc: " + ccc);
this.ccc = ccc;
}
}
С следующей XML-конфигурацией:
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A">
<property name="bbb" ref="bBean" />
<property name="ccc" ref="cBean" />
</bean>
Загрузка контекста даст следующий вывод:
создание бина B: com.xxx.B@c2ff5
создание бина C: com.xxx.C@1e8a1f6
создание бина A: com.yyy.A@1e152c5
установка A.bbb: com.xxx.B@c2ff5
установка A.ccc: com.xxx.C@1e8a1f6
Это ожидаемый результат. Однако это «старый стиль» Spring. Теперь у нас есть аннотации, и давайте использовать их, чтобы упростить XML.
Сначала я внедрю свойства bbb
и ccc
в бине A
, сделав это следующим образом:
package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.B;
import com.xxx.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("создание бина A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("установка A.bbb: " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("установка A.ccc: " + ccc);
this.ccc = ccc;
}
}
Это позволяет мне убрать следующие строки из XML:
<property name="bbb" ref="bBean" />
<property name="ccc" ref="cBean" />
Теперь мой XML упрощен до следующего:
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />
Когда я загружаю контекст, я получаю следующий вывод:
создание бина B: com.xxx.B@5e5a50
создание бина C: com.xxx.C@54a328
создание бина A: com.yyy.A@a3d4cf
Это неверно! Что произошло? Почему мои свойства не были внедрены?
На самом деле, аннотации — это хорошая функция, но сами по себе они ничего не делают. Они просто аннотируют элементы. Вам нужен инструмент обработки, чтобы находить аннотации и выполнять с ними действия.
На помощь приходит <context:annotation-config>
. Это активирует действия для аннотаций, которые находятся на бинах, определенных в том же контексте приложения, где он сам определен.
Если я изменю мой XML на это:
<context:annotation-config />
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />
При загрузке контекста я получаю правильный результат:
создание бина B: com.xxx.B@15663a2
создание бина C: com.xxx.C@cd5f8b
создание бина A: com.yyy.A@157aa53
установка A.bbb: com.xxx.B@15663a2
установка A.ccc: com.xxx.C@cd5f8b
Хорошо, это неплохо, но я убрал две строки из XML и добавил одну. Не такая уж большая разница. Идея аннотаций заключается в том, чтобы уменьшить количество XML.
Поэтому давайте уберем определения в XML и заменим их все аннотациями:
package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class B {
public B() {
System.out.println("создание бина B: " + this);
}
}
package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class C {
public C() {
System.out.println("создание бина C: " + this);
}
}
package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xxx.B;
import com.xxx.C;
@Component
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("создание бина A: " + this);
}
@Autowired
public void setBbb(B bbb) {
System.out.println("установка A.bbb: " + bbb);
this.bbb = bbb;
}
@Autowired
public void setCcc(C ccc) {
System.out.println("установка A.ccc: " + ccc);
this.ccc = ccc;
}
}
В XML мы оставляем только это:
<context:annotation-config />
Загружаем контекст, и результат... Ничего. Никакие бины не созданы, никакие бины не внедрены. Ничего!
Это происходит потому, как я уже сказал в первом абзаце, <context:annotation-config />
работает только с бинами, зарегистрированными в контексте приложения. Поскольку я убрал XML-конфигурации для трех бинов, ни один бин не создан, и <context:annotation-config />
не имеет «целей», с которыми может работать.
Но это не проблема для <context:component-scan>
, который может сканировать пакет на наличие «целей», с которыми можно работать. Давайте изменим содержимое XML-конфигурации на следующий элемент:
<context:component-scan base-package="com.xxx" />
Когда я загружаю контекст, я получаю следующий вывод:
создание бина B: com.xxx.B@1be0f0a
создание бина C: com.xxx.C@80d1ff
Хммм... что-то не хватает. Почему?
Если внимательно посмотреть на классы, класс A
находится в пакете com.yyy
, но я указал в <context:component-scan>
использовать пакет com.xxx
, поэтому мой класс A
был полностью пропущен, и были обнаружены только B
и C
, которые находятся в пакете com.xxx
.
Чтобы исправить это, я добавляю также другой пакет:
<context:component-scan base-package="com.xxx,com.yyy" />
Теперь мы получим ожидаемый результат:
создание бина B: com.xxx.B@cd5f8b
создание бина C: com.xxx.C@15ac3c9
создание бина A: com.yyy.A@ec4a87
установка A.bbb: com.xxx.B@cd5f8b
установка A.ccc: com.xxx.C@15ac3c9
И на этом все! Теперь у вас нет определений в XML, у вас есть аннотации.
В качестве финального примера, сохранив аннотированные классы A
, B
и C
, добавим следующее в XML, что мы получим после загрузки контекста?
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
Мы все равно получим правильный результат:
создание бина B: com.xxx.B@157aa53
создание бина C: com.xxx.C@ec4a87
создание бина A: com.yyy.A@1d64c37
установка A.bbb: com.xxx.B@157aa53
установка A.ccc: com.xxx.C@ec4a87
Даже если бин для класса A
не был получен с помощью сканирования, инструменты обработки все равно применяются к <context:component-scan>
ко всем бинам, зарегистрированным в контексте приложения, даже к A
, который был вручную зарегистрирован в XML.
Но что если у нас будет следующая XML, получим ли мы дублированные бины, потому что мы указали как <context:annotation-config />
, так и <context:component-scan>
?
<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
Нет, дублирований не будет. Мы снова получим ожидаемый результат:
создание бина B: com.xxx.B@157aa53
создание бина C: com.xxx.C@ec4a87
создание бина A: com.yyy.A@1d64c37
установка A.bbb: com.xxx.B@157aa53
установка A.ccc: com.xxx.C@ec4a87
Это происходит потому, что оба тега регистрируют одни и те же инструменты обработки (<context:annotation-config />
можно опустить, если указано <context:component-scan>
), но Spring заботится о том, чтобы они выполнялись только один раз.
Даже если вы несколько раз зарегистрируете инструменты обработки сами, Spring все равно позаботится о том, чтобы они выполнили свои действия только один раз; этот XML:
<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
<bean id="bla" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla1" class="org.springframework.beans.factory
Spring предлагает два ключевых механизма:
- Автовнедрение (Autowiring) бинов
- Автообнаружение (Autodiscovery) бинов
1. Автовнедрение (Autowiring)
Обычно в applicationContext.xml вы определяете бины, а остальные бины подключаются с помощью методов-конструкторов или сеттеров. Вы можете подключать бины как через XML, так и с помощью аннотаций. Если вы используете аннотации, вам нужно активировать их, добавив <context:annotation-config />
в applicationContext.xml. Это упростит структуру тега из applicationContext.xml, поскольку вам не придется вручную подключать бины (через конструкторы или сеттеры). Вы можете использовать аннотацию @Autowired
, и бины будут подключены по типу.
Следующий шаг к избавлению от ручной конфигурации XML — это
2. Автообнаружение (Autodiscovery)
Автообнаружение упрощает конфигурацию XML еще больше, тем что вам даже не нужно добавлять тег <bean>
в applicationContext.xml. Вы просто помечаете конкретные бины одной из следующих аннотаций: @Controller, @Service, @Component, @Repository. Используя <context:component-scan>
и указав базовый пакет, Spring автоматически обнаружит и подключит помеченные компоненты и их зависимости в контейнер Spring.
В заключение:
<context:annotation-config />
используется для возможности применения аннотации @Autowired<context:component-scan />
используется для определения поиска конкретных бинов и попытки автовнедрения.
Разница между этими двумя элементами действительно проста!
<context:annotation-config />
Эта команда позволяет использовать аннотации, которые ограничены связыванием только свойств и конструкторов бинов.
В то время как
<context:component-scan base-package="org.package"/>
Эта команда включает в себя все возможности <context:annotation-config />
, но дополнительно позволяет использовать стереотипы, такие как @Component
, @Service
, @Repository
. Это дает возможность связывать целые бины, а не только ограничиваться конструкторами или свойствами.
<context:annotation-config>
лишь разрешает аннотации @Autowired
и @Qualifier
, и это все, о чем идет речь — о внедрении зависимостей. Существуют и другие аннотации, выполняющие ту же задачу, такие как @Inject
, но суть остается той же — разрешение ВЗ через аннотации.
Имейте в виду, что даже при объявлении элемента <context:annotation-config>
вы все равно должны объявить свой класс как Bean, помните, у нас есть три доступных варианта:
- XML:
<bean>
- Аннотации:
@Component
,@Service
,@Repository
,@Controller
- JavaConfig:
@Configuration
,@Bean
Теперь перейдем к <context:component-scan>
, который выполняет две задачи:
- Сканирует все классы, аннотированные
@Component
,@Service
,@Repository
,@Controller
и@Configuration
, и создает Bean. - Выполняет ту же работу, что и
<context:annotation-config>
.
Таким образом, если вы объявили <context:component-scan>
, больше нет необходимости также объявлять <context:annotation-config>
.
Обычный сценарий может выглядеть так: вы объявляете только bean через XML и разрешаете ВЗ через аннотации, например:
<bean id="serviceBeanA" class="com.something.CarServiceImpl" />
<bean id="serviceBeanB" class="com.something.PersonServiceImpl" />
<bean id="repositoryBeanA" class="com.something.CarRepository" />
<bean id="repositoryBeanB" class="com.something.PersonRepository" />
Мы только что объявили бины, ничего не сказав о <constructor-arg>
и <property>
, ВЗ настраивается в самих классах через @Autowired
. Это означает, что сервисы используют @Autowired
для своих репозиторных компонентов, а репозитории используют @Autowired
для компонентов JdbcTemplate, DataSource и т.д.
Значение <context:component-scan />
неявно включает <context:annotation-config/>
. Если вы попробуете использовать <context:component-scan base-package="..." annotation-config="false"/>
, то в вашей конфигурации аннотации @Service, @Repository, @Component будут работать корректно, но аннотации @Autowired, @Resource и @Inject не будут работать.
Это означает, что AutowiredAnnotationBeanPostProcessor не будет активирован, и контейнер Spring не сможет обработать аннотации автозависимости.
Если вы хотите использовать автозависимости, убедитесь, что вы включили аннотации конфигурации, либо установив annotation-config="true"
, либо просто убрав этот атрибут, поскольку по умолчанию он включен.
Spring: @Component против @Bean
Что значит "Не удалось найти или загрузить основной класс"?
Почему в RecyclerView отсутствует onItemClickListener()?
Что значит 'synchronized'?
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?