7

Разница между <context:annotation-config> и <context:component-scan>

5

Я изучаю Spring 3, и у меня возникли трудности с пониманием функциональности тегов <context:annotation-config> и <context:component-scan>.

Судя по тому, что я прочитал, эти теги обрабатывают разные аннотации (такие как @Required, @Autowired и т.д. против @Component, @Repository, @Service и т.д.), но также, как я выяснил, они регистрируют одни и те же bean post processor классы.

Чтобы запутать меня еще больше, в <context:component-scan> есть атрибут annotation-config.

Может кто-то объяснить, в чем разница и сходство между этими тегами? Является ли один из них устаревшим по сравнению с другим, дополняют ли они друг друга, нужно ли мне использовать один из них или оба?

5 ответ(ов)

14

<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
0

Spring предлагает два ключевых механизма:

  1. Автовнедрение (Autowiring) бинов
  2. Автообнаружение (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 /> используется для определения поиска конкретных бинов и попытки автовнедрения.
0

Разница между этими двумя элементами действительно проста!

<context:annotation-config /> 

Эта команда позволяет использовать аннотации, которые ограничены связыванием только свойств и конструкторов бинов.

В то время как

<context:component-scan base-package="org.package"/> 

Эта команда включает в себя все возможности <context:annotation-config />, но дополнительно позволяет использовать стереотипы, такие как @Component, @Service, @Repository. Это дает возможность связывать целые бины, а не только ограничиваться конструкторами или свойствами.

0

<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 и т.д.

0

Значение <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", либо просто убрав этот атрибут, поскольку по умолчанию он включен.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь