代码咖啡因的个人博客 代码咖啡因的个人博客

记录精彩的程序人生

目录
SpringDI(依赖注入)的三种方式
/    

SpringDI(依赖注入)的三种方式

依赖注入(Dependency Injection)的三种核心方式详解


一、构造器注入(Constructor Injection)

1. 定义与实现

  • 核心逻辑:通过类的构造函数传递依赖对象,强制在对象创建时完成依赖注入。

  • Spring实现:

    @Service  
    public class OrderService {  
        private final PaymentService paymentService;  
        // 构造器声明依赖  
        public OrderService(PaymentService paymentService) {  
            this.paymentService  = paymentService;  
        }  
    }  
    

    或在XML中配置:

    <bean id="orderService" class="com.example.OrderService">   
        <constructor-arg ref="paymentService"/>  
    </bean>  
    

2. 优势与适用场景

  • 不可变性:依赖字段可设为final,确保线程安全与对象完整性。
  • 强契约性:避免部分初始化状态(依赖必须全部在构造时提供)。
  • 推荐场景:
    • 核心必选依赖
    • 需要不可变状态的组件(如工具类、配置类)

3. 局限性

  • 循环依赖问题:若类A和类B互相通过构造器注入,Spring默认无法解决(需改用Setter注入或@Lazy)。

二、Setter注入(Setter Injection)

1. 定义与实现

  • 核心逻辑:通过Setter方法动态设置依赖,允许对象在创建后修改依赖。

  • Spring实现:

    public class UserService {  
        private UserRepository userRepository;  
        // Setter方法声明依赖  
        @Autowired  
        public void setUserRepository(UserRepository userRepository) {  
            this.userRepository  = userRepository;  
        }  
    }  
    

    XML配置:

    <bean id="userService" class="com.example.UserService">   
        <property name="userRepository" ref="jdbcUserRepository"/>  
    </bean>  
    

2. 优势与适用场景

  • 灵活性:支持依赖的动态更新(如热部署场景)。
  • 可选依赖:可配合@Autowired(required=false)实现非强制注入。
  • 推荐场景:
    • 可选或可替换的依赖(如策略模式实现)
    • 需要重新配置的组件(如运行时切换数据源)

3. 局限性

  • 状态可变风险:依赖可能在对象生命周期中被意外修改。
  • 时序依赖:需确保Setter方法在业务逻辑前调用。

三、字段注入(Field Injection)

1. 定义与实现

  • 核心逻辑:直接通过字段(成员变量)注入依赖,无需显式Setter或构造器。
  • Spring实现:
    @Service  
    public class ProductService {  
        @Autowired  
        private InventoryService inventoryService;  
    }  
    

2. 优势与适用场景

  • 代码简洁性:减少样板代码,适合快速开发。
  • 最小侵入:无需修改构造器或Setter方法。
  • 推荐场景:
    • 原型验证或小型项目
    • 框架内部组件(如JPA Repository)

3. 局限性

  • 隐藏依赖:依赖关系不通过公共接口暴露,增加代码理解成本。
  • 测试困难:必须通过反射或Spring容器才能注入Mock对象。
  • 违反单一职责原则:易导致类过度依赖容器。

四、三种方式对比与选型建议

维度构造器注入Setter注入字段注入
不可变性✅ 支持final字段❌ 依赖可变❌ 依赖可变
代码可读性明确声明所有依赖显式Setter方法依赖关系隐蔽
循环依赖处理不支持(默认)支持支持
单元测试易通过参数传递Mock需调用Setter需反射或容器
Spring官方推荐优先使用(尤其必选依赖)可选依赖场景谨慎使用(避免过度依赖)

五、高级实践与扩展

  1. 混合使用策略
    • 对必选依赖使用构造器注入,可选依赖使用Setter注入(如配置开关)。
    • 示例:
      public class NotificationService {  
          private final EmailSender emailSender;  // 必选  
          private SMSSender smsSender;            // 可选  
          public NotificationService(EmailSender emailSender) {  
              this.emailSender  = emailSender;  
          }  
          @Autowired(required = false)  
          public void setSmsSender(SMSSender smsSender) {  
              this.smsSender  = smsSender;  
          }  
      }  
      
  2. Java Config优化
    • 在@Configuration类中显式配置Bean依赖链,增强可控性:
      @Bean  
      public OrderService orderService(PaymentService paymentService) {  
          return new OrderService(paymentService);  
      }  
      
  3. Lombok辅助构造器注入
    • 结合@RequiredArgsConstructor简化代码:
      @Service  
      @RequiredArgsConstructor  
      public class AuthService {  
          private final UserRepository userRepository;  
          private final PasswordEncoder passwordEncoder;  
      }  
      

总结:依赖注入是Spring实现控制反转的核心手段,三种方式各有适用场景。构造器注入因其安全性和明确性成为现代Spring应用的首选方案,Setter注入在动态配置场景仍有价值,而字段注入应作为辅助手段有限使用。理解其差异与适用边界,有助于构建更健壮、可维护的应用程序架构。