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

722
我正在学习Spring3,但似乎不太明白<context:annotation-config><context:component-scan>的功能是什么。
据我所知,它们处理不同的注释(如@Required@Autowired等与@Component@Repository@Service等),但是从我的阅读中得出,它们都注册相同的bean后置处理器类。
更让我困惑的是,在<context:component-scan>上有一个annotation-config属性。
有人能解释一下这些标签吗?它们之间有什么相似之处,有什么不同之处,其中一个是否被另一个取代,它们是否互补,我需要其中之一还是两者都需要呢?

总结一下:尽可能使用 component-scan - Jerry Chin
15个回答

1485

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

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

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

让我们从基本设置开始,其中三个类型为ABC的bean被定义,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

好的,这是预期的输出。但这是“旧式”的Spring。现在我们有注释,所以让我们使用它们来简化XML。

首先,让我们像这样自动装配bean 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类,只选择了BC,它们在com.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没有通过扫描获得,处理工具仍然会被应用于应用程序上下文中注册的所有bean,即使是在XML中手动注册的A。
但是,如果我们有以下XML,我们会得到重复的bean吗?因为我们同时指定了和?
<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> 的工作方式。


10
如果已经指定了context:component-scan,则可以省略<context:annotation-config />。那么为什么还要使用annotation-config呢?它的存在有何意义? - CodeClimber
3
好的回答!没有什么比简短明了的例子配以简洁的描述更好了。一次阅读就完全理解了整个内容。 - Jigish
27
我希望你能写整本 Spring 手册!关于任何与令人困惑的 Spring 框架有关的事情,这是最好的解释。谢谢。 (翻译完毕,无返回内容) - eskalera
8
非常简洁明了的解释。除了得到答案,我还学会了清晰表达事物的好方法 :) - Amir
3
你的写作风格非常适合初学者理解。我希望你能撰写一本关于基础Spring的书籍,我保证会购买它。 - emeraldhieu
显示剩余25条评论

170

我发现了这个不错的概述,其中列出了哪些声明使用了哪些注释。通过学习它,您会发现 <context:component-scan/><context:annotation-config/> 能够识别更多的注释,即:

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

正如您所看到的,<context:component-scan/> 逻辑上扩展了 <context:annotation-config/> ,它具有CLASSPATH组件扫描和Java @Configuration特性。


99

Spring允许你做两件事情:

  1. 自动装配Bean
  2. 自动发现Bean

1. 自动装配
通常在applicationContext.xml中定义Beans,其他的Beans通过构造函数或者setter方法进行连接。你可以使用XML或注释来连接Beans。 如果你使用注释,你需要激活注释并且将<context:annotation-config />添加到applicationContext.xml中。这将简化applicationContext.xml<bean>标签的结构,因为你不必手动连接Beans(构造函数或setter)。你可以使用@Autowire注释来按类型连接Beans。

这是摆脱手动XML配置的一步

2. 自动发现
自动发现在进一步简化XML方面更进一步,因为你甚至不需要在applicationContext.xml中添加<bean>标签。你只需要用以下任意一种注释标记特定的Beans,Spring将自动连接标记的Beans及其依赖项到Spring容器中。注释如下:@Controller@Service@Component@Repository。通过使用<context:component-scan>并指定基础包,Spring将自动发现和连接组件到Spring容器中。


总之:

  • <context:annotation-config />用于启用@Autowired注释
  • <context:component-scan />用于确定特定Beans的搜索和尝试自动装配。

1
有没有可能在使用component-scan的同时不使用annotation-config呢? - Koray Tugay
1
在上下文中使用annotation-config标签时,将其设置为"false"。 - Sara

39

请问您能否进一步解释一下?如果我使用<context:component-scan>,那么我就无法使用XML覆盖bean定义了吗? - user938214097
@user938214097,你可以通过XML或使用组件扫描的注释来定义bean。 - Sean Patrick Floyd
仅使用<context:component-scan>是否足够?如果我不使用<context:annotation-config>,会失去一些东西吗? - user938214097
@Tomasz 似乎已经回答了。 - Sean Patrick Floyd

34

<context:annotation-config>: 扫描并激活Spring配置文件中已注册bean的注解。

<context:component-scan>: Bean注册 + <context:annotation-config>


@Autowired和@Required目标属性级别的,因此在使用这些注释之前,bean应该在Spring IOC中注册。要启用这些注释,必须注册相应的bean或包括<context:annotation-config />。即<context:annotation-config />仅与已注册的bean一起使用。

@Required启用RequiredAnnotationBeanPostProcessor处理工具
@Autowired启用AutowiredAnnotationBeanPostProcessor处理工具

注意: 注解本身没有任何作用,我们需要一个处理工具,它位于注解下面的一个类中,负责核心处理过程。


@Repository、@Service和@Controller是@Component,它们的目标类级别

<context:component-scan>扫描包并查找注册bean,并且它包括<context:annotation-config />完成的工作。

从XML迁移到注解


31

两者之间的区别非常简单!

<context:annotation-config /> 

使你能够使用仅限于连接bean的属性和构造函数的注释!

然而,

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

启用所有 <context:annotation-config /> 的功能,同时使用注解来实现例如@Component@Service@Repository等的功能。因此,您可以连接整个bean而不仅仅是构造函数或属性!


19
<context:annotation-config>

解析@Autowired@Qualifier注释,仅此而已,这与依赖注入有关。还有其他可以执行相同工作的注释,例如@Inject,但所有这些都是通过注释来解析DI。

请注意,即使您声明了<context:annotation-config>元素,您仍然必须将您的类声明为Bean,记住我们有三个可用选项:

  • XML:<bean>
  • @Annotations:@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>

就这些。

一个常见的场景是仅通过XML声明一个Bean,并通过注释解析DI,例如:

<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" />

我们只声明了bean,没有涉及到<constructor-arg><property>,依赖注入是在它们自己的类中通过@Autowired配置的。这意味着服务使用@Autowired注入其存储库组件,而存储库则使用@Autowired注入JdbcTemplate、DataSource等组件。


1
非常好的解释。谢谢@Manuel Jordan。 - Bala

15
<context:annotation-config> 标签告诉Spring扫描代码库以自动解决包含@Autowired注释的类的依赖关系要求。
Spring 2.5还添加了对JSR-250注释的支持,例如@Resource,@PostConstruct和@PreDestroy。使用这些注释还需要在Spring容器中注册某些BeanPostProcessors。与往常一样,这些可以注册为单独的bean定义,但也可以通过在spring配置中包含标签来隐式注册它们。
摘自基于注释的配置的Spring文档。
Spring提供自动检测“模式化”类并向ApplicationContext注册相应的BeanDefinitions的功能。
根据org.springframework.stereotype的javadoc:
Stereotypes是指类型或方法在总体架构中(在概念层面上而不是实现层面上)的角色的注释。例如:@Controller @Service @Repository等。这些旨在供工具和方面使用(成为切入点的理想目标)。
要自动检测这样的“模式化”类,需要<context:component-scan>标签。 <context:component-scan>标签还告诉Spring扫描指定包(及其所有子包)下的可注入bean的代码。

7
<context:component-scan /> implicitly enables <context:annotation-config/>

尝试使用<context:component-scan base-package="..." annotation-config="false"/>在你的配置中,@Service,@Repository,@Component可以正常工作,但@Autowired,@Resource和@Inject无法工作。
这意味着AutowiredAnnotationBeanPostProcessor将不会启用,Spring容器将不会处理自动装配注释。

这个帮助我理解<context:component-scan />隐式启用context:annotation-config/;也就是说,它扫描bean定义并执行所需的注入。我尝试了annotation-config="false",除非我显式地使用context:annotation-config/设置,否则注入不起作用。最终,我的理解比以前更好了! - CuriousMind

5
<context:annotation-config/> <!-- is used to activate the annotation for beans -->
<context:component-scan base-package="x.y.MyClass" /> <!-- is for the Spring IOC container to look for the beans in the base package. -->

另一个需要注意的重要点是,context:component-scan 隐式调用 context:annotation-config 来激活 bean 上的注解。如果您不希望 context:component-scan 自动为您激活注解,可以将 context:component-scan 的 annotation-config 元素设置为 false
总结一下:
<context:annotation-config/> <!-- activates the annotations --> 
<context:component-scan base-package="x.y.MyClass" /> <!-- activates the annotations + register the beans by looking inside the base-package -->

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接