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的完整生命周期:
- 实例化(Instantiation)
- 属性填充(Population)
- 初始化(Initialization)
- 销毁(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提供多种解析策略:
- 精确名称匹配(优先使用)
@Autowired
private PaymentProcessor alipayProcessor;
- @Qualifier注解限定
@Autowired
@Qualifier("wechatPayment")
private PaymentProcessor processor;
- 自定义限定注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface WechatQualifier {}
@Bean
@WechatQualifier
public PaymentProcessor wechatProcessor() {
return new WechatProcessor();
}
2.3 循环依赖问题详解
Spring通过三级缓存解决属性注入的循环依赖:
- 一级缓存:singletonObjects(完整Bean)
- 二级缓存:earlySingletonObjects(早期引用)
- 三级缓存:singletonFactories(对象工厂)
解决流程示例(A依赖B,B依赖A):
- 创建A实例(未完成)
- 注入B时触发B的创建
- 创建B实例(未完成)
- 注入A时从三级缓存获取A的早期引用
- B完成初始化
- 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修饰符确保:
- 依赖在构造时完成初始化
- 避免后续意外修改
- 线程安全保证
3.3 多参数处理策略
当构造方法参数超过3个时,建议:
- 使用Builder模式封装参数
- 创建配置参数对象
- 拆分类职责(可能违反单一职责原则的信号)
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
排查步骤:
- 检查组件扫描路径
- 确认@Bean定义是否正确
- 验证依赖是否在容器初始化之后使用
- 查看条件化配置是否生效
8.2 BeanCurrentlyInCreationException
解决方案:
- 使用@Lazy延迟加载
- 改为setter注入
- 重构代码消除循环依赖
- 调整Bean初始化顺序
8.3 注入类型不匹配
调试方法:
- 执行
applicationContext.getBeanNamesForType()
查看所有候选Bean - 检查泛型类型擦除问题
- 验证@Qualifier值是否匹配
- 确认是否有多余的@Primary注解