依赖注入的时候,直接Autowired比较直接,为什么推荐构造方法注入呢

7 20~26 min

在依赖注入中,推荐使用构造方法注入而非直接使用 @Autowired 字段注入,主要原因如下:


1. 不可变性(Immutability)

  • 构造方法注入允许将依赖字段声明为 final,确保依赖在对象创建后不可变。这符合“不可变对象”的设计原则,避免了因依赖被意外修改而导致的潜在风险。

  • 字段注入无法保证这一点,因为依赖是通过反射设置的,无法直接标记为 final


2. 明确的依赖关系

  • 构造方法强制在对象创建时传入所有必需依赖,确保对象在初始化后处于完全可用状态。这种显式的依赖关系提高了代码的可读性和可维护性。

  • 字段注入隐藏了依赖关系,可能导致开发者忽略某些依赖是否实际被注入。


3. 更早发现循环依赖

  • 如果存在循环依赖(如 A 依赖 B,B 又依赖 A),构造方法注入会在应用启动时直接抛出 BeanCurrentlyInCreationException,帮助开发者尽早发现问题。

  • 字段注入通过 Spring 的三级缓存机制掩盖了循环依赖,可能导致运行时出现难以调试的问题。


4. 更好的单元测试支持

  • 使用构造方法注入时,可以手动通过 new 创建对象并传入 Mock 依赖(如 Mockito 对象),无需依赖 Spring 容器。

  • 字段注入需要借助反射或 Spring 测试框架,增加了测试复杂度。


5. 单一职责原则(SRP)的促进

  • 构造方法参数过多时,可能提示类的职责过重(违反 SRP),促使开发者重构代码。

  • 字段注入因没有参数列表,容易掩盖类的臃肿问题。


6. 兼容性更广

  • 构造方法注入不依赖 Spring 框架的特性,更符合纯 Java 的设计模式,便于迁移到其他框架或脱离框架使用。


示例对比

字段注入(不推荐)

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository; // 依赖隐藏,无法标记为 final
}

构造方法注入(推荐)

@Service
public class UserService {
    private final UserRepository userRepository;

    // 显式依赖,强制初始化
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

何时使用其他注入方式?

  • Setter 注入:适用于可选依赖(如配置类),但需避免滥用。

  • 字段注入:仅推荐在简单原型代码或遗留项目中使用。


总结

构造方法注入通过强制依赖不可变、显式声明依赖关系、支持更好的测试和设计,成为依赖注入的最佳实践。而字段注入虽然便捷,但隐藏了依赖管理的关键细节,可能导致代码脆弱性。建议结合 Lombok 的 @RequiredArgsConstructor 简化构造方法代码,兼顾简洁性与健壮性。


当需要注入多个依赖时,构造方法注入的用法依然简单且清晰,以下是详细说明和示例:


1. 基本用法:直接声明多个参数

在构造方法的参数列表中列出所有需要注入的依赖,Spring 会自动按类型匹配并注入。

示例代码

@Service
public class OrderService {
    private final UserRepository userRepository;
    private final ProductRepository productRepository;
    private final PaymentService paymentService;

    // 构造方法注入多个依赖
    public OrderService(
        UserRepository userRepository,
        ProductRepository productRepository,
        PaymentService paymentService
    ) {
        this.userRepository = userRepository;
        this.productRepository = productRepository;
        this.paymentService = paymentService;
    }
}

2. 使用 Lombok 简化代码

如果依赖较多,可以通过 Lombok 的 @RequiredArgsConstructor 自动生成构造方法,避免手动编写冗余代码。

步骤

  1. 添加 Lombok 依赖(确保项目中已集成)。

  2. 在类上添加 @RequiredArgsConstructor 注解。

  3. 将所有需要注入的字段标记为 final

示例代码

@Service
@RequiredArgsConstructor // 自动生成包含所有 final 字段的构造方法
public class OrderService {
    private final UserRepository userRepository;
    private final ProductRepository productRepository;
    private final PaymentService paymentService;
}

3. 处理依赖冲突(同类型 Bean)

如果存在多个同类型的 Bean(例如多个 DataSource 实现),需通过 @Qualifier 指定具体 Bean 名称。

示例代码

@Service
public class OrderService {
    private final DataSource primaryDataSource;
    private final DataSource backupDataSource;

    // 使用 @Qualifier 指定具体 Bean
    public OrderService(
        @Qualifier("primaryDataSource") DataSource primaryDataSource,
        @Qualifier("backupDataSource") DataSource backupDataSource
    ) {
        this.primaryDataSource = primaryDataSource;
        this.backupDataSource = backupDataSource;
    }
}

4. 参数顺序和可选依赖

  • 参数顺序无关:Spring 根据类型(和名称)匹配依赖,与构造方法参数的顺序无关。

  • 可选依赖:如果某个依赖是可选的(非必需),可以结合 @Autowired(required = false) 标记,但需注意空指针问题。


5. 处理大量依赖时的优化建议

如果构造方法参数过多(例如超过 5 个),可能是类违反了单一职责原则(SRP),建议重构:

  • 将部分依赖合并为一个聚合类(如 OrderDependencies)。

  • 将某些逻辑拆分到独立的服务中。

重构示例

// 将依赖聚合为一个类
public class OrderDependencies {
    private final UserRepository userRepository;
    private final ProductRepository productRepository;
    private final PaymentService paymentService;
    // 构造方法 + getter
}

// 使用聚合类简化 OrderService
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderDependencies dependencies;
}

6. 单元测试中的使用

构造方法注入使得单元测试更简单,即使依赖较多,也可以直接通过 new 创建对象并传入 Mock 依赖。

测试示例

public class OrderServiceTest {
    @Test
    void testCreateOrder() {
        // 创建 Mock 依赖
        UserRepository userRepoMock = mock(UserRepository.class);
        ProductRepository productRepoMock = mock(ProductRepository.class);
        PaymentService paymentServiceMock = mock(PaymentService.class);

        // 通过构造方法注入 Mock
        OrderService orderService = new OrderService(
            userRepoMock,
            productRepoMock,
            paymentServiceMock
        );

        // 执行测试逻辑
        orderService.createOrder(...);
    }
}

总结

  • 多个依赖注入:直接在构造方法中声明所有参数,Spring 自动注入。

  • 简化代码:使用 Lombok 的 @RequiredArgsConstructor 避免手动编写构造方法。

  • 处理冲突:通过 @Qualifier 指定同名 Bean。

  • 重构建议:参数过多时考虑拆分职责。

通过构造方法注入,既能保证依赖的不可变性明确性,又能轻松应对多依赖场景,是 Spring 中最推荐的依赖注入方式。