在 Spring Boot 项目中,依赖注入是核心功能之一,@Resource@Autowiredfinal 是常见的依赖注入方式。它们各有特点和适用场景:


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@Resourcefinal
注入方式字段、构造器、Setter字段、Setter构造器
注入策略byType(默认),支持 @QualifierbyName(默认),支持 name 属性不可变依赖(需配合构造器注入)
不可变性
可测试性一般(字段注入需反射)一般(字段注入需反射)高(构造器注入直接传入 Mock)
代码可读性一般(依赖关系隐式)一般(依赖关系隐式)高(依赖关系显式)
线程安全否(依赖可变)否(依赖可变)是(依赖不可变)
适用场景灵活场景(支持所有注入方式)按名称注入的简单场景强制依赖、不可变对象的场景
推荐程度构造器注入(推荐)简单场景构造器注入(推荐)

5. 最佳实践建议

  1. 优先使用构造器注入@Autowiredfinal):

    • 强制依赖显式化,确保对象初始化时依赖完整。
    • 不可变性:结合 final 关键字,提高代码健壮性。
    • 可测试性:直接通过构造器传入 Mock 对象,便于单元测试。
  2. 合理使用 @Resource

    • 按名称注入:适合需要明确指定 Bean 名称的场景。
    • 简单场景:如配置类或工具类的注入。
  3. 慎用字段注入

    • 可测试性差:依赖字段需通过反射设置,增加测试复杂度。
    • 隐藏依赖关系:代码可读性降低,不利于维护。
  4. 避免循环依赖

    • 构造器注入 + final:可避免循环依赖问题,因为依赖在初始化时必须就绪。
    • 字段注入:需通过 @Lazy@DependsOn 解决循环依赖。
  5. 结合 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,确保依赖不可变、显式且易于测试,避免字段注入和复杂依赖关系。