Spring依赖注入:属性注入、构造方法与Setter注入对比指南

一、依赖注入的本质与核心价值

1.1 控制反转(IoC)的具象化实现

依赖注入(Dependency Injection)是控制反转(Inversion of Control)设计思想的具体实现方式。传统编程中对象主动创建依赖项:

// 传统方式:直接创建依赖
public class OrderService {
    private PaymentProcessor processor = new AlipayProcessor();
}

而采用IoC容器后:

// IoC方式:依赖由容器注入
public class OrderService {
    @Autowired
    private PaymentProcessor processor;
}

这种转变使得类不再负责自身依赖的创建,转而由外部容器统一管理,实现了控制权的反转。

1.2 解耦程度对比分析

通过解耦系数公式可以量化DI带来的改进:

耦合度 = (直接依赖数 / 总依赖数) × 100%

假设系统有200个类,每个类平均依赖5个其他类:

  • 传统方式耦合度:100%
  • DI方式耦合度:≈12%(通过接口间接依赖)

1.3 生命周期管理优势

Spring容器管理Bean的完整生命周期:

  1. 实例化(Instantiation)
  2. 属性填充(Population)
  3. 初始化(Initialization)
  4. 销毁(Destruction)

容器在属性填充阶段完成依赖注入,确保Bean在使用时所有依赖都已就绪。

二、属性注入深度解析

2.1 @Autowired的运作机制

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, 
        ElementType.PARAMETER, ElementType.FIELD, 
        ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

注解的required属性默认为true,当找不到匹配Bean时抛出NoSuchBeanDefinitionException。设置为false则允许null值注入。

2.2 多Bean匹配解决方案

当存在多个同类型Bean时,Spring提供多种解析策略:

  1. 精确名称匹配(优先使用)
@Autowired
private PaymentProcessor alipayProcessor;
  1. @Qualifier注解限定
@Autowired
@Qualifier("wechatPayment")
private PaymentProcessor processor;
  1. 自定义限定注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface WechatQualifier {}

@Bean
@WechatQualifier
public PaymentProcessor wechatProcessor() {
    return new WechatProcessor();
}

2.3 循环依赖问题详解

Spring通过三级缓存解决属性注入的循环依赖:

  1. 一级缓存:singletonObjects(完整Bean)
  2. 二级缓存:earlySingletonObjects(早期引用)
  3. 三级缓存:singletonFactories(对象工厂)

解决流程示例(A依赖B,B依赖A):

  1. 创建A实例(未完成)
  2. 注入B时触发B的创建
  3. 创建B实例(未完成)
  4. 注入A时从三级缓存获取A的早期引用
  5. B完成初始化
  6. A完成初始化

但构造方法注入无法解决循环依赖,因为此时对象尚未创建完成。

三、构造方法注入最佳实践

3.1 官方推荐依据

Spring官方文档明确建议:

"Always use constructor based dependency injection for mandatory dependencies. Only use setter injection for optional dependencies."

3.2 不可变(Immutable)依赖实现

public class OrderService {
    private final PaymentProcessor processor;
    private final InventoryService inventory;

    @Autowired
    public OrderService(PaymentProcessor processor, 
                       InventoryService inventory) {
        this.processor = processor;
        this.inventory = inventory;
    }
}

使用final修饰符确保:

  1. 依赖在构造时完成初始化
  2. 避免后续意外修改
  3. 线程安全保证

3.3 多参数处理策略

当构造方法参数超过3个时,建议:

  1. 使用Builder模式封装参数
  2. 创建配置参数对象
  3. 拆分类职责(可能违反单一职责原则的信号)

3.4 Lombok简化方案

@RequiredArgsConstructor
public class ShippingService {
    private final AddressValidator validator;
    private final LogisticsCalculator calculator;
    private final TrackingService tracking;
}

@RequiredArgsConstructor自动生成包含final字段的构造方法,保持代码简洁。

四、Setter注入适用场景分析

4.1 可选依赖配置

public class CachingService {
    private CacheManager cacheManager;

    @Autowired(required = false)
    public void setCacheManager(CacheManager mgr) {
        this.cacheManager = mgr;
    }
}

当CacheManager不存在时,自动跳过注入,避免启动失败。

4.2 动态重新配置

配合RefreshScope实现配置热更新:

@RefreshScope
public class ConfigService {
    private String apiKey;

    @Autowired
    public void setApiKey(@Value("${api.key}") String key) {
        this.apiKey = key;
    }
}

4.3 接口注入模式

public interface Configurable {
    void setConfiguration(Config config);
}

@Component
public class AppConfigurer implements Configurable {
    private Config config;

    @Autowired
    @Override
    public void setConfiguration(Config config) {
        this.config = config;
    }
}

实现接口注入,增强扩展性。

五、混合注入策略实践

5.1 强制依赖与可选依赖组合

public class PaymentGateway {
    private final AlipayService alipay;  // 强制依赖
    private WechatPayService wechat;     // 可选依赖

    @Autowired
    public PaymentGateway(AlipayService alipay) {
        this.alipay = alipay;
    }

    @Autowired(required = false)
    public void setWechatPay(WechatPayService wechat) {
        this.wechat = wechat;
    }
}

5.2 生命周期阶段控制

public class DataInitializer {
    private final DataSource dataSource; // 启动时必需
    private CacheProvider cache;         // 运行时可选

    @Autowired
    public DataInitializer(DataSource ds) {
        this.dataSource = ds;
    }

    @PostConstruct
    public void initCache() {
        if (cache != null) {
            cache.warmUp();
        }
    }

    @Autowired
    public void setCache(CacheProvider cache) {
        this.cache = cache;
    }
}

六、高级依赖管理技巧

6.1 延迟注入(Lazy)

public class ReportGenerator {
    @Lazy
    @Autowired
    private ComplexCalculator calculator;
}

首次访问calculator时才进行初始化,适用于:

  • 循环依赖无法解决时
  • 资源密集型对象
  • 条件性使用的依赖

6.2 泛型依赖注入

public abstract class AbstractRepository<T> {
    @Autowired
    private CrudRepository<T, Long> repository;
}

@Repository
public class UserRepository extends AbstractRepository<User> {}

Spring通过泛型类型保留(Generics Preservation)机制自动匹配具体实现。

6.3 条件化装配

@Configuration
public class PaymentConfig {
    @Bean
    @ConditionalOnProperty(name = "payment.provider", havingValue = "alipay")
    public PaymentProcessor alipayProcessor() {
        return new AlipayProcessor();
    }

    @Bean
    @ConditionalOnExpression("#{T(java.time.LocalTime).now().getHour() > 12}")
    public DiscountStrategy afternoonDiscount() {
        return new SpecialDiscount();
    }
}

七、测试中的依赖注入

7.1 单元测试模拟

public class OrderServiceTest {
    @Mock
    private PaymentProcessor processor;
    
    @InjectMocks
    private OrderService service;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void testPayment() {
        when(processor.process(any())).thenReturn(true);
        assertTrue(service.placeOrder(new Order()));
    }
}

7.2 集成测试配置

@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
public class IntegrationTest {
    @Autowired
    private DataService dataService;

    @TestConfiguration
    static class TestConfig {
        @Bean
        @Primary
        public DataSource testDataSource() {
            return new EmbeddedDatabaseBuilder()
                  .setType(EmbeddedDatabaseType.H2)
                  .build();
        }
    }
}

八、常见问题排查指南

8.1 NoSuchBeanDefinitionException

排查步骤:

  1. 检查组件扫描路径
  2. 确认@Bean定义是否正确
  3. 验证依赖是否在容器初始化之后使用
  4. 查看条件化配置是否生效

8.2 BeanCurrentlyInCreationException

解决方案:

  1. 使用@Lazy延迟加载
  2. 改为setter注入
  3. 重构代码消除循环依赖
  4. 调整Bean初始化顺序

8.3 注入类型不匹配

调试方法:

  1. 执行applicationContext.getBeanNamesForType()查看所有候选Bean
  2. 检查泛型类型擦除问题
  3. 验证@Qualifier值是否匹配
  4. 确认是否有多余的@Primary注解
正文到此结束
评论插件初始化中...
Loading...