Разница между <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
Где находится аннотация @Transactional?
Сходства и различия между аннотациями в Java и атрибутами в C#
Что значит 'synchronized'?
Как объявить массив в одну строку?