在 Spring Boot 项目中,依赖注入是核心功能之一,@Resource、@Autowired 和 final 是常见的依赖注入方式。它们各有特点和适用场景:
1. @Autowired
1.1 机制
- Spring 框架提供的注解,用于自动注入依赖。
- 默认按类型(byType)注入:Spring 容器会查找与目标字段/方法参数类型匹配的 Bean。
- 支持字段、构造器、Setter 方法注入。
- 可与
@Qualifier结合:当存在多个同类型 Bean 时,通过@Qualifier指定名称。
1.2 优点
- 灵活性高:支持字段、构造器、Setter 方法注入。
- 与 Spring 深度集成:是 Spring 的原生注解,功能更全面。
- 可控制注入时机:通过
required属性(@Autowired(required = false))控制依赖是否可选。
1.3 缺点
- 字段注入的可测试性差:直接注入字段时,单元测试需依赖 Spring 容器或反射。
- 依赖关系不透明:字段注入隐藏了依赖关系,代码可读性较低。
- 不支持不可变性:依赖字段可被修改,可能导致状态不一致。
1.4 使用场景
- 构造器注入(推荐):确保依赖在对象创建时初始化,适合强制依赖。
@Service public class OrderService { private final PaymentService paymentService; @Autowired public OrderService(PaymentService paymentService) { this.paymentService = paymentService; } } - Setter 注入:适合可选依赖或动态修改依赖。
@Service public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
2. @Resource
2.1 机制
- Java 标准注解(JSR-250),由 JDK 提供,非 Spring 原生注解。
- 默认按名称(byName)注入:优先匹配字段名或方法名对应的 Bean。
- 支持字段和 Setter 方法注入。
- 无需与
@Qualifier结合:直接通过名称指定 Bean。
2.2 优点
- 跨框架兼容性:不依赖 Spring,适合多框架混合项目。
- 按名称注入更直观:明确指定 Bean 名称,减少歧义。
- 代码简洁:无需额外注解(如
@Qualifier)。
2.3 缺点
- 灵活性较低:无法直接控制注入顺序或依赖的可选性。
- 不支持构造器注入:只能用于字段或 Setter 方法。
- 与 Spring 功能结合较少:部分高级特性(如
@Lazy)不兼容。
2.4 使用场景
- 字段注入:适合简单场景,尤其是需要按名称注入时。
@Service public class CustomerService { @Resource(name = "customerRepositoryImpl") private CustomerRepository customerRepository; } - Setter 注入:与
@Autowired类似,但按名称注入。@Service public class ProductService { private ProductRepository productRepository; @Resource public void setProductRepository(ProductRepository productRepository) { this.productRepository = productRepository; } }
3. final 关键字
3.1 机制
- Java 语言特性,用于声明不可变变量。
- 与构造器注入结合使用:依赖在对象初始化时赋值,且不可修改。
- 强制依赖显式化:依赖必须在构造器中传入,避免隐式依赖。
3.2 优点
- 不可变性:依赖一旦初始化即不可变,避免运行时修改导致的不可预测行为。
- 线程安全:不可变对象天然线程安全,适合并发场景。
- 可测试性高:构造器注入便于单元测试(直接传入 Mock 对象)。
- 代码清晰:依赖关系显式化,提高可读性和可维护性。
3.3 缺点
- 构造器参数冗长:依赖较多时,构造器参数列表过长。
- Lombok 依赖:需配合
@RequiredArgsConstructor简化代码。
3.4 使用场景
- 构造器注入 +
final:推荐用于强制依赖,确保对象完全初始化。@Service public class InventoryService { private final ProductRepository productRepository; public InventoryService(ProductRepository productRepository) { this.productRepository = productRepository; } } - 与 Lombok 结合:减少样板代码。
@Service @RequiredArgsConstructor public class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService; }
4. 三者对比总结
| 特性 | @Autowired | @Resource | final |
|---|---|---|---|
| 注入方式 | 字段、构造器、Setter | 字段、Setter | 构造器 |
| 注入策略 | byType(默认),支持 @Qualifier | byName(默认),支持 name 属性 | 不可变依赖(需配合构造器注入) |
| 不可变性 | 否 | 否 | 是 |
| 可测试性 | 一般(字段注入需反射) | 一般(字段注入需反射) | 高(构造器注入直接传入 Mock) |
| 代码可读性 | 一般(依赖关系隐式) | 一般(依赖关系隐式) | 高(依赖关系显式) |
| 线程安全 | 否(依赖可变) | 否(依赖可变) | 是(依赖不可变) |
| 适用场景 | 灵活场景(支持所有注入方式) | 按名称注入的简单场景 | 强制依赖、不可变对象的场景 |
| 推荐程度 | 构造器注入(推荐) | 简单场景 | 构造器注入(推荐) |
5. 最佳实践建议
-
优先使用构造器注入(
@Autowired或final):- 强制依赖显式化,确保对象初始化时依赖完整。
- 不可变性:结合
final关键字,提高代码健壮性。 - 可测试性:直接通过构造器传入 Mock 对象,便于单元测试。
-
合理使用
@Resource:- 按名称注入:适合需要明确指定 Bean 名称的场景。
- 简单场景:如配置类或工具类的注入。
-
慎用字段注入:
- 可测试性差:依赖字段需通过反射设置,增加测试复杂度。
- 隐藏依赖关系:代码可读性降低,不利于维护。
-
避免循环依赖:
- 构造器注入 +
final:可避免循环依赖问题,因为依赖在初始化时必须就绪。 - 字段注入:需通过
@Lazy或@DependsOn解决循环依赖。
- 构造器注入 +
-
结合 Lombok 简化代码:
- 使用
@RequiredArgsConstructor生成构造器,减少样板代码。
- 使用
6. 示例对比
6.1 构造器注入(推荐)
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
6.2 字段注入(不推荐)
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
}
6.3 @Resource 字段注入
@Service
public class OrderService {
@Resource(name = "paymentService")
private PaymentService paymentService;
}
7. 总结
@Autowired:Spring 原生注解,灵活但需注意字段注入的缺点。@Resource:标准注解,适合按名称注入的简单场景。final:结合构造器注入,强制依赖显式化,提高代码质量。
推荐实践:优先使用 构造器注入 + final,确保依赖不可变、显式且易于测试,避免字段注入和复杂依赖关系。