自动装配协作对象 (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,则会引发致命错误。 |
对于 byType 或 constructor 自动装配模式,你可以装配数组和类型化集合。在这种情况下,容器内所有匹配预期类型的自动装配候选者都会被提供以满足依赖。如果预期的键类型是 String,你可以自动装配强类型的 Map 实例。自动装配的 Map 实例的值由所有匹配预期类型的 Bean 实例组成,而 Map 实例的键包含相应的 Bean 名称。
自动装配的局限性与缺点
当在整个项目中一致地使用自动装配时,它的效果最好。如果通常不使用自动装配,而仅用于装配一两个 Bean 定义,可能会给开发人员带来困惑。
考虑到自动装配的局限性和缺点:
- 显式装配总是覆盖自动装配:
property和constructor-arg设置中的显式依赖项总是会覆盖自动装配。你不能自动装配简单属性,如基本类型、String和Class(以及此类简单属性的数组)。这一限制是设计使然。 - 不如显式装配精确:尽管如前面的表所述,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 属性的显式值 true 或 false 总是优先。对于此类 Bean,模式匹配规则不适用。
这些技术对于那些你永远不想通过自动装配注入到其他 Bean 中的 Bean 非常有用。这并不意味着排除的 Bean 本身不能使用自动装配进行配置。相反,该 Bean 本身不是自动装配其他 Bean 的候选者。
6.2 版本新特性
从 6.2 版本开始,@Bean 方法支持自动装配候选者标志的两种变体:autowireCandidate 和 defaultCandidate。
- 使用限定符(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 的基础)。
- 尴尬时刻:当你为了做读写分离定义了两个
DataSource(masterDataSource,slaveDataSource)时,所有依赖DataSource的 Bean 都会因为“由于存在两个候选者”而崩溃。 - 解决之道:这时就必须配合使用
primary="true"或者autowire-candidate="false"来人为消除歧义。
4. 自动装配候选者的妙用 假设你定义了一个 InternalUtilityBean,它只供内部某些特定配置使用,不希望它被其他业务 Bean 意外地通过类型注入。
- 操作:给它加上
autowire-candidate="false"。 - 理解:这相当于把它从“公共人才市场”下架了,但你依然可以通过 ID 显式地聘用(引用)它。
5. 现代开发的视角 虽然本章讲的是 XML 里的 autowire 属性,但其实理解了这些逻辑,你就理解了 @Autowired 的核心。现代开发中,我们几乎 100% 使用注解。但记住:原理是通用的。Spring 依然会先按类型找,找不到或找多了再按名称/限定符找。