协慌网

登录 贡献 社区

<context:annotation-config> 和 <context:component-scan> 之间的区别

我正在学习 Spring 3,并且似乎不太了解<context:annotation-config><context:component-scan>背后的功能。

从我阅读的内容来看,它们似乎处理不同的注释@Required@Autowired等与@Component Component, @Service Repository, @Repository等),但是从我阅读的内容来看,它们注册了相同的bean 后处理器类。

更让我感到困惑的是, <context:component-scan> annotation-config属性

有人可以阐明这些标签吗?有什么相似之处,有什么不同之处,一个被另一个取代,它们彼此完成,我是否需要其中一个?

答案

<context:annotation-config>用于激活已经在应用程序上下文中注册的 bean 中的注释(无论它们是使用 XML 定义还是通过包扫描定义的)。

<context:component-scan>也可以执行<context:annotation-config>操作,但是<context:component-scan>也会扫描软件包以在应用程序上下文中查找并注册 bean。

我将使用一些示例来显示差异 / 相似之处。

让我们从基本设置三个类型为ABC BC注入A

package com.xxx;
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
public class C {
  public C() {
    System.out.println("creating bean 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("creating bean A: " + this);
  }
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + 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>

加载上下文会产生以下输出:

creating bean B: com.xxx.B@c2ff5
creating bean C: com.xxx.C@1e8a1f6
creating bean A: com.yyy.A@1e152c5
setting A.bbb with com.xxx.B@c2ff5
setting A.ccc with com.xxx.C@1e8a1f6

好的,这是预期的输出。但这是 “旧风格” 的春天。现在我们有了注释,因此让我们使用它们来简化 XML。

首先,让我们自动装配A bbbccc属性,如下所示:

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("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + 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" />

当我加载上下文时,我得到以下输出:

creating bean B: com.xxx.B@5e5a50
creating bean C: com.xxx.C@54a328
creating bean A: com.yyy.A@a3d4cf

好的,这是错误的!发生了什么?为什么我的房屋不自动接线?

好吧,注解是一个不错的功能,但就其本身而言,它们什么也没做。他们只是注释东西。您需要一个处理工具来查找批注并对其进行处理。

<context:annotation-config>进行救援。这将激活对在定义自身的相同应用程序上下文中定义的 bean 上发现的注释的操作。

如果我将 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" />

当我加载应用程序上下文时,我得到正确的结果:

creating bean B: com.xxx.B@15663a2
creating bean C: com.xxx.C@cd5f8b
creating bean A: com.yyy.A@157aa53
setting A.bbb with com.xxx.B@15663a2
setting A.ccc with com.xxx.C@cd5f8b

好的,这很好,但是我已经从 XML 中删除了两行并添加了一行。那不是很大的区别。带注释的想法是应该删除 XML。

因此,让我们删除 XML 定义并将其全部替换为注释:

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class C {
  public C() {
    System.out.println("creating bean 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("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc;
  }
}

在 XML 中,我们仅保留以下内容:

<context:annotation-config />

我们加载上下文,结果是…… 一无所有。没有创建 bean,也没有自动装配 bean。没有什么!

这是因为,正如我在第一段中所述, <context:annotation-config />仅适用于在应用程序上下文中注册的 bean。因为我删除了这三个 bean 的 XML 配置,所以没有创建 bean,并且<context:annotation-config />没有要处理的 “目标”。

<context:component-scan>不是问题,它可以扫描软件包以查找 “目标” 以进行处理。让我们将 XML 配置的内容更改为以下条目:

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

当我加载上下文时,我得到以下输出:

creating bean B: com.xxx.B@1be0f0a
creating bean C: com.xxx.C@80d1ff

嗯... 有些东西丢失了。为什么?

如果您仔细观察这些类,则类A具有包com.yyy但是我已经在<context:component-scan>指定使用包com.xxx因此这完全错过了我的A类,只选择了BCcom.xxx软件包上。

为了解决这个问题,我还添加了另一个软件包:

<context:component-scan base-package="com.xxx,com.yyy" />

现在我们得到了预期的结果:

creating bean B: com.xxx.B@cd5f8b
creating bean C: com.xxx.C@15ac3c9
creating bean A: com.yyy.A@ec4a87
setting A.bbb with com.xxx.B@cd5f8b
setting A.ccc with com.xxx.C@15ac3c9

就是这样!现在,您不再具有 XML 定义,而具有注释。

作为最后一个示例,保留带注释的类ABC并将以下内容添加到 XML 中,加载上下文后会得到什么?

<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />

我们仍然得到正确的结果:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

A类的 Bean,处理工具仍会由<context:component-scan>应用于在应用程序上下文中注册的所有 Bean,即使对于已在 XML 中手动注册的A

但是,如果我们具有以下 XML,将会得到重复的 bean,因为我们同时指定了<context:annotation-config /><context:component-scan>吗?

<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />

不,没有重复,我们再次得到了预期的结果:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

这是因为两个标签都注册了相同的处理工具(如果指定了<context:component-scan> <context:annotation-config /> ),但是 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.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla2" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla3" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />

仍会产生以下结果:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@25d2b2
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

好的,关于说唱。

我希望这些信息以及 @Tomasz Nurkiewicz 和 @Sean Patrick Floyd 的答复都是理解<context:annotation-config><context:component-scan>工作方式所需要的。

我发现了关于哪些注释是由哪些声明选择的一个很好的摘要。通过研究,您会发现<context:component-scan/> <context:annotation-config/>识别的注释的超集,即:

  • @Component@Service@Repository@Controller@Endpoint
  • @Configuration@Bean@Lazy@Scope@Order@Primary@Profile@DependsOn@Import@ImportResource

如您所见, <context:component-scan/> /> 具有 CLASSPATH 组件扫描和 Java @Configuration 功能,从逻辑上扩展了<context:annotation-config/>

Spring 使您可以做两件事:

  1. 自动接线
  2. 自动发现豆

1. 自动接线
通常,您在 applicationContext.xml 中定义 bean,而其他 bean 是使用构造函数或 setter 方法连接的。您可以使用 XML 或注释对 bean 进行接线。如果使用批注,则需要激活批注,并且必须在applicationContext.xml 中<context:annotation-config /> 。这将简化applicationContext.xml 中标记的结构,因为您不必手动连接 bean(构造函数或 setter)。您可以使用@Autowire注释,并且将按类型连接 bean。

转义手动 XML 配置的步骤是

2. 自动发现
从某种意义上讲,您甚至不需要在 applicationContext.xml 中<bean>标记,自动发现使 XML 进一步简化了一步。您只需使用以下注释之一标记特定的 bean,Spring 就会自动将标记的 bean 及其依赖项连接到 Spring 容器中。注释如下: @Controller@ Service,@Component@Repository 。通过使用<context:component-scan>并指向基本包,Spring 将自动发现并将组件连接到 Spring 容器中。


作为结论:

  • <context:annotation-config />用于能够使用@Autowired注释
  • <context:component-scan />用于确定特定 bean 的搜索以及自动装配的尝试。