引言
在 Spring 应用中,依赖注入(DI)是控制反转(IoC)的核心功能。注解驱动的自动装配极大简化了对象的管理与组装工作。最常见的两个注解是 @Autowired(Spring 原生 / 兼容 JSR-330)和 @Resource(JSR-250),它们看似相似,但语义、解析时机和细节行为都不同。本文把原理、常见用法、差异、进阶特性、常见坑与最佳实践全部拆开讲清,并配合代码示例,使你在工程中能正确选型与排错。
目标读者:具备 Spring 基础的后端开发者,想把自动装配从"会用"升级为"懂原理与最佳实践"。
一、自动装配(Autowiring)概念回顾
自动装配的目标是由容器负责将依赖(Bean)注入到需要它的类中,开发者只需声明依赖,而不用显式 new、lookup 或管理生命周期。注解驱动的自动装配主要靠容器在应用上下文启动期间解析注解并满足依赖。
Spring 中常用的方式有:
- 基于注解:
@Autowired,@Resource,@Inject - 基于 XML:
<bean autowire="byName|byType">(旧方式,不推荐) - 基于配置类:
@Bean方法的参数注入
自动装配分为按类型(byType)、按名称(byName)和按注解/限定符(qualifier)等策略。
二、@Autowired 深度解析
@Autowired 是 Spring 提供的自动注入注解(也被 Spring 当作 JSR-330 的 @Inject 的实现),常见于字段、构造器、方法(setter / 任意方法)上。
核心要点:
-
按类型注入(byType):默认行为是按类型查找匹配的 bean。
-
Required 属性 :
@Autowired(required = true)(默认)表示必须找到匹配依赖,否则容器启动失败(抛出NoSuchBeanDefinitionException)。required = false表示找不到时注入null(字段/setter)或跳过(构造器不适用)。 -
Qualifier 支持 :配合
@Qualifier("beanName")或自定义注解进行更精确的注入。 -
构造器注入(推荐) :Spring(自 4.3 起)若
@Component只有一个构造器且构造器上没有@Autowired,仍会自动注入构造器参数(隐式注入)。 -
泛型支持 :能注入
List<MyService>、Map<String, MyService>等集合类型,集合会注入所有符合类型的 beans。 -
Optional 支持 :能注入
java.util.Optional<T>(Spring 会把可能的 bean 包装成 Optional)。 -
处理顺序 :注入是在 Bean 的实例化与属性填充阶段(Instantiation -> Populate -> Initialize),
@Autowired的解析实际由AutowiredAnnotationBeanPostProcessor处理。 -
注解解析器 :Spring 在容器启动时注册
AutowiredAnnotationBeanPostProcessor,扫描 bean 的注入点并在依赖解析时尝试注入。
代码示例:字段与构造器注入
java
@Component
public class OrderService {
// 字段注入(不推荐)
@Autowired
private PaymentService paymentService;
// 构造器注入(推荐)
private final InventoryService inventoryService;
@Autowired // 可以省略(如果只有一个构造器)
public OrderService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
}
required=false 用法
java
@Autowired(required = false)
private CacheService cacheService; // 若无 Bean,可为 null
@Qualifier 配合
java
@Component("fast")
public class FastPaymentService implements PaymentService {}
@Component("safe")
public class SafePaymentService implements PaymentService {}
@Autowired
@Qualifier("safe")
private PaymentService paymentService; // 注入 SafePaymentService
三、@Resource 深度解析
@Resource 来自 JSR-250(javax.annotation.Resource),Spring 对其提供了支持。它的解析不同于 @Autowired:
核心要点:
-
优先按名称查找(byName) :
@Resource的默认行为是先用name属性(或注入点名称)去容器中查找 bean;如果找不到,再按类型匹配。也就是说@Resource更偏"按名注入"。 -
属性:
@Resource的常用属性是name和type。name指定 bean 名称,type指定 bean 类型(少用)。
-
等价映射:
- 当你写
@Resource在字段private Foo bar;上,Spring 会尝试查找名为"bar"的 bean(字段名),如果找不到则按类型Foo去找。
- 当你写
-
解析器 :由
CommonAnnotationBeanPostProcessor处理。
代码示例
java
@Component("myPaymentService")
public class PaymentServiceImpl implements PaymentService {}
public class OrderService {
@Resource // 默认按 name = "paymentService"(字段名)
private PaymentService paymentService;
@Resource(name = "myPaymentService")
private PaymentService explicitPayment; // 指定 bean 名称
}
注意点:
-
@Resource不支持required=false的属性;如果使用按名称找不到 bean 会抛异常(除非你配合Optional/其它方式)。 -
@Resource对于同类型多个 bean 的场景常通过name显式指定,避免歧义。
四、@Inject(JSR-330)简要比较
@Inject(javax.inject.Inject)是 JSR-330 规范的一部分,语义类似 @Autowired(按类型注入),但没有 required 属性(可结合 @Nullable 或 Optional)。Spring 也支持 @Inject,背后也是由 AutowiredAnnotationBeanPostProcessor 解析。
五、@Autowired vs @Resource --- 关键差异表
| 方面 | @Autowired |
@Resource |
|---|---|---|
| 来源 | Spring | JSR-250 |
| 注入策略 | 按类型(byType)为主(可按名称 via @Qualifier) |
先按名称(byName),找不到则按类型(byType) |
是否有 required |
是(required) |
无(没有 required 属性) |
| 支持限定符 | 支持 @Qualifier |
支持 name 属性 |
| 处理器 | AutowiredAnnotationBeanPostProcessor |
CommonAnnotationBeanPostProcessor |
| 推荐场景 | Spring 项目首选(灵活) | 更偏向 Java EE 兼容或按 bean 名称注入场景 |
工程建议 :在 Spring 应用里,优先使用 @Autowired(配合 @Qualifier / @Primary),@Resource 适合需要按名称绑定或与 Java EE 习惯兼容的场景。
六、注入方式:构造器 / setter / 字段注入(比较与推荐)
1) 构造器注入(推荐)
优点:
- 强制依赖(final 字段,可确保不可变)
- 便于单元测试(通过构造器传参注入 mock)
- 避免反射设置字段、提高明确性
- 与不可变对象设计契合
示例:
java
@Component
public class A {
private final B b;
public A(B b) { this.b = b; } // Spring 注入 B
}
2) Setter 注入(可选)
优点:
适合可选依赖或需要延迟注入的场景
缺点:
依赖可能在对象创建后被注入,导致状态不一致风险
示例:
java
@Component
public class A {
private B b;
@Autowired
public void setB(B b) { this.b = b; }
}
3) 字段注入(不推荐,常见于示例/快速原型)
优点:
写法简洁
缺点:
-
难以进行单元测试(必须用反射或 Spring Test)
-
隐式依赖,降低可维护性与可测试性
示例:
java
@Component
public class A {
@Autowired
private B b;
}
结论 :优先使用构造器注入,在需要可选依赖时使用 setter 注入;避免字段注入于生产代码。
七、进阶特性
@Qualifier 与 @Primary
-
@Qualifier("name")用于在按类型注入出现多个 bean 时提供进一步的区分(精确到 bean 名称或自定义限定注解)。 -
@Primary标注在某个 bean 上,表示当按类型自动装配却没有@Qualifier时优先选择该 bean。
示例:
java
@Component
@Primary
public class DefaultPaymentService implements PaymentService {}
@Component("fast")
public class FastPaymentService implements PaymentService {}
@Autowired
private PaymentService paymentService; // 注入 DefaultPaymentService
@Autowired
@Qualifier("fast")
private PaymentService fastPayment; // 注入 FastPaymentService
集合注入(List / Map)
-
List<MyService>会注入容器内所有MyService类型的 bean(按注册顺序)。 -
Map<String, MyService>会注入 beanName -> beanInstance 的映射。
java
@Autowired
private List<MyService> services;
@Autowired
private Map<String, MyService> serviceMap;
可选注入(Optional / required=false)
-
Optional<T>:Spring 能注入 Optional.empty() 或 Optional.of(bean)。 -
@Autowired(required=false):当不存在依赖时注入 null(字段/方法)。 -
@Nullable:Spring 支持@Nullable参数注入以表示可空。
延迟注入(@Lazy)
@Lazy可以标注在 bean 定义或注入点,表示延迟创建或延迟解析依赖,有助于优化启动速度或避免循环依赖(在某些场景)。
java
@Autowired
@Lazy
private HeavyService heavyService;
注入代理(AOP / ScopedProxy)
当注入作用域为 @RequestScope / @SessionScope 到单例 bean 时,Spring 会使用代理(scoped proxy)注入,以便在运行时根据请求/会话返回正确实例。
八、循环依赖与解决方案
循环依赖示例(A -> B -> A)
-
字段注入 / setter 注入:Spring 能通过提前暴露半成品(early reference)解决循环依赖(通过三级缓存:singletonObjects, earlySingletonObjects, singletonFactories)。
-
构造器注入 :构造器注入无法解决循环依赖(因为实例需要在构造时完成),会导致
BeanCurrentlyInCreationException。
解决方式:
-
重新设计避免循环依赖(推荐)
-
使用 setter 注入替代构造器注入(如果不可避免)
-
使用
@Lazy让其中一个依赖延迟注入 -
用接口/事件解耦(更好的设计)
九、测试中的自动装配
在单元测试中尽量避免启动整个 Spring 容器(重量级)。常用策略:
-
使用 Mockito 做纯单元测试:构造器注入令替换 mock 更简单。
-
使用 Spring Test 切片 (
@WebMvcTest,@DataJpaTest)只加载需要的组件。 -
使用
@SpringBootTest(整合测试):启动完整上下文,适合集成测试。 -
替换 bean :用
@MockBean或@TestConfiguration覆盖真实 bean。
示例(Mockito + 构造器注入):
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentService payment;
@Mock InventoryService inventory;
@InjectMocks
OrderService orderService; // Mockito 用构造器注入 mock
}
十、最佳实践与常见反模式
推荐做法(Best Practices)
- 优先使用 构造器注入(明确、易测试、支持不可变字段)。
- 使用
@Autowired+@Qualifier/@Primary协同解决同类型冲突。 - 避免在生产代码中使用字段注入(只用于示例或快速原型)。
- 当需要按名称绑定时使用
@Resource(name="..."),或在 Spring 项目中多数情况仍用@Qualifier。 - 对于可选依赖使用
Optional<T>或@Autowired(required=false)+@Nullable。 - 避免循环依赖,通过架构设计或事件/接口解耦。
- 在注入 request/session 范围 bean 到单例 bean 时使用 scoped proxy。
- 在启动性能敏感场景谨慎使用大量
@Lazy,并测量影响。
常见反模式(Avoid)
- 大量字段注入,导致类隐式依赖茂盛,难以测试。
- 把业务逻辑隐藏在自动装配后,造成强耦合(例如容器配置驱动业务流程)。
- 在 app 启动阶段依赖容器注入的静态工具(静态注入不推荐)。
- 不显式标注
@Qualifier或@Primary导致不确定注入,间接引入 bug。
总结
@Autowired(按类型)、@Resource(按名称优先)、@Inject(JSR-330)都用于依赖注入,但语义/解析顺序不同。
构造器注入是首选,setter 注入用于可选依赖,字段注入应避免。
使用 @Qualifier / @Primary、集合注入、Optional 或 required=false 能解决常见注入冲突或可选依赖问题。
循环依赖是常见坑,构造器注入不能解决,优先通过架构调整避免。
在测试中优先用构造器注入 + Mockito 做纯单元测试,集成测试再用 Spring Test。