Skip to content

自动装配协作对象 (Autowiring Collaborators)

Spring 容器可以自动装配(Autowire)协作 Bean 之间的关系。你可以通过检查 ApplicationContext 的内容,让 Spring 自动为你的 Bean 解析协作对象(其他 Bean)。

自动装配具有以下优点:

  • 减少配置:自动装配可以显著减少指定属性或构造函数参数的需求。(本章其他地方讨论的 Bean 模板等机制在这方面也很有价值。)
  • 同步更新:随着对象的演进,自动装配可以自动更新配置。例如,如果你需要为类添加依赖项,该依赖项可以自动满足,而无需你修改配置。因此,自动装配在开发过程中特别有用,而且当代码库变得更加稳定时,也不排除切换到显式装配的选项。

当使用基于 XML 的配置元数据时(参见依赖注入),你可以使用 <bean/> 元素的 autowire 属性来为 Bean 定义指定自动装配模式。自动装配功能有四种模式。你可以为每个 Bean 指定自动装配,从而选择要自动装配哪些 Bean。下表描述了这四种自动装配模式:

模式说明
no(默认)无自动装配。Bean 引用必须由 ref 元素定义。对于较大的部署,不建议更改默认设置,因为显式指定协作对象可以获得更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName按属性名称自动装配。Spring 寻找与需要自动装配的属性同名的 Bean。例如,如果一个 Bean 定义设置为按名称自动装配,并且它包含一个 master 属性(即它有一个 setMaster(..) 方法),Spring 就会寻找名为 master 的 Bean 定义并使用它来设置属性。
byType如果容器中恰好存在一个该属性类型的 Bean,则允许自动装配属性。如果存在多个,则抛出致命异常,这表明你不能对该 Bean 使用 byType 自动装配。如果没有匹配的 Bean,则什么也不会发生(属性不会被设置)。
constructor类似于 byType,但适用于构造函数参数。如果容器中没有恰好一个构造函数参数类型的 Bean,则会引发致命错误。

对于 byTypeconstructor 自动装配模式,你可以装配数组和类型化集合。在这种情况下,容器内所有匹配预期类型的自动装配候选者都会被提供以满足依赖。如果预期的键类型是 String,你可以自动装配强类型的 Map 实例。自动装配的 Map 实例的值由所有匹配预期类型的 Bean 实例组成,而 Map 实例的键包含相应的 Bean 名称。

自动装配的局限性与缺点

当在整个项目中一致地使用自动装配时,它的效果最好。如果通常不使用自动装配,而仅用于装配一两个 Bean 定义,可能会给开发人员带来困惑。

考虑到自动装配的局限性和缺点:

  • 显式装配总是覆盖自动装配propertyconstructor-arg 设置中的显式依赖项总是会覆盖自动装配。你不能自动装配简单属性,如基本类型、StringClass(以及此类简单属性的数组)。这一限制是设计使然。
  • 不如显式装配精确:尽管如前面的表所述,Spring 会小心避免在可能产生意外结果的歧义情况下进行猜测,但受 Spring 管理的对象之间的关系不再被明确记录。
  • 文档缺失:工具可能无法获得装配信息,无法从 Spring 容器生成文档。
  • 存在多个匹配项:容器内的多个 Bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组、集合或 Map 实例,这不一定是个问题。然而,对于期望单个值的依赖项,这种歧义无法被随意解析。如果没有唯一的 Bean 定义可用,则抛出异常。

在后一种情况下,你有几种选择:

  • 放弃自动装配,改用显式装配。
  • 通过将 Bean 定义的 autowire-candidate 属性设置为 false 来避免该 Bean 被自动装配,如下一节所述。
  • 通过将 <bean/> 元素的 primary 属性设置为 true,将单个 Bean 定义指定为主要候选者。
  • 实现基于注解的配置中可用的更细粒度的控制,如基于注解的容器配置中所述。

将 Bean 排除在自动装配之外

你可以按 Bean 排除在自动装配之外。在 Spring 的 XML 格式中,将 <bean/> 元素的 autowire-candidate 属性设置为 false;使用 @Bean 注解时,该属性名为 autowireCandidate。容器使该特定的 Bean 定义对自动装配基础设施不可用,包括基于注解的注入点,如 @Autowired

注意autowire-candidate 属性旨在仅影响基于类型的自动装配。它不影响按名称的显式引用,即使指定的 Bean 未被标记为自动装配候选者,显式引用也会被解析。因此,如果名称匹配,按名称自动装配仍然会注入 Bean。

你还可以基于针对 Bean 名称的模式匹配来限制自动装配候选者。顶级 <beans/> 元素在其 default-autowire-candidates 属性中接受一个或多个模式。例如,要将自动装配候选状态限制为名称以 Repository 结尾的任何 Bean,请提供值 *Repository。要提供多个模式,请在逗号分隔的列表中定义它们。Bean 定义的 autowire-candidate 属性的显式值 truefalse 总是优先。对于此类 Bean,模式匹配规则不适用。

这些技术对于那些你永远不想通过自动装配注入到其他 Bean 中的 Bean 非常有用。这并不意味着排除的 Bean 本身不能使用自动装配进行配置。相反,该 Bean 本身不是自动装配其他 Bean 的候选者。

6.2 版本新特性

从 6.2 版本开始,@Bean 方法支持自动装配候选者标志的两种变体:autowireCandidatedefaultCandidate

  • 使用限定符(Qualifiers)时,标记为 defaultCandidate=false 的 Bean 仅适用于存在额外限定符指示的注入点。这对于受限代理非常有用。此类 Bean 永远不会仅通过声明的类型注入,而是需要通过类型加上特定限定符来注入。
  • 相比之下,autowireCandidate=false 的行为与上述 autowire-candidate 属性完全相同:此类 Bean 根本不会按类型注入。

补充教学 —— 自动装配的艺术与坑

1. 为什么“显式”由于“自动”? 在 Spring 的 XML 时代,大牛们通常建议将 autowire 设置为 no(默认值)。

  • 透明性:在 XML 里一眼就能看到 A 依赖 B,B 依赖 C。
  • 可维护性:自动装配(尤其是 byType)在项目变大、同类型 Bean 变多时,极其容易抛出 NoUniqueBeanDefinitionException,导致系统启动失败。

2. byName 的“潜规则” 如果你有一个 setOrderService(OrderService os),而在 XML 里正好有个 ID 为 orderService 的 Bean,它就会被塞进去。

  • 坑点:如果有人把 ID 改成了 myOrderService,自动装配就静默失效了(属性为 null),直到运行时报错。

3. byType 的尴尬 这是最常用的自动装配方式(也是 @Autowired 的基础)。

  • 尴尬时刻:当你为了做读写分离定义了两个 DataSourcemasterDataSource, slaveDataSource)时,所有依赖 DataSource 的 Bean 都会因为“由于存在两个候选者”而崩溃。
  • 解决之道:这时就必须配合使用 primary="true" 或者 autowire-candidate="false" 来人为消除歧义。

4. 自动装配候选者的妙用 假设你定义了一个 InternalUtilityBean,它只供内部某些特定配置使用,不希望它被其他业务 Bean 意外地通过类型注入。

  • 操作:给它加上 autowire-candidate="false"
  • 理解:这相当于把它从“公共人才市场”下架了,但你依然可以通过 ID 显式地聘用(引用)它。

5. 现代开发的视角 虽然本章讲的是 XML 里的 autowire 属性,但其实理解了这些逻辑,你就理解了 @Autowired 的核心。现代开发中,我们几乎 100% 使用注解。但记住:原理是通用的。Spring 依然会先按类型找,找不到或找多了再按名称/限定符找。

Based on Spring Framework.