一、概念介绍
Spring 提供了 4 种主要的依赖注入(DI)方式,核心目标是将对象的创建和依赖关系交给 Spring 容器管理,实现控制反转(IoC)。
1.构造函数注入(Constructor Injection)⭐推荐
用法示例
java
@Service
public class OrderService {
private final UserRepository userRepository;
private final PaymentService paymentService;
// Spring 自动识别构造函数进行注入
public OrderService(UserRepository userRepository,
PaymentService paymentService) {
this.userRepository = userRepository;
this.paymentService = paymentService;
}
}
// Lombok 简化写法
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserRepository userRepository;
private final PaymentService paymentService;
}
特点
✅ 强制依赖:对象创建时必须提供所有依赖
✅ 不可变性:字段可用 final 修饰
✅ 易测试:可直接 new 对象,无需 Spring 容器
❌ 循环依赖:无法直接解决(需配合 @Lazy)
2.Setter 注入(Setter Injection)
用法示例
java
@Service
public class NotificationService {
private EmailService emailService;
private SmsService smsService;
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false) // 可选依赖
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
}
特点
✅ 可选依赖:可标注 required = false
✅ 灵活配置:可在运行时重新设置依赖
⚠️ 可变性:依赖可能被修改
❌ 代码冗长:每个依赖需要一个 setter 方法
3.字段注入(Field Injection)❌不推荐
用法示例
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
@Lazy // 解决循环依赖
private OrderService orderService;
}
特点
✅ 代码简洁:最少样板代码
❌ 隐藏依赖:无法从外部看出类需要哪些依赖
❌ 难测试:必须启动 Spring 容器或使用反射
❌ 违反设计原则:无法使用 final,违背单一职责
4.接口注入(Interface Injection)
用法示例
java
// 定义注入接口
public interface BeanNameAware {
void setBeanName(String name);
}
@Service
public class MyService implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name; // Spring 容器调用
}
}
特点
⚠️ 侵入性强:类必须实现特定接口
⚠️ 使用场景少:主要用于 Spring 内部机制
❌ 不常用:现代 Spring 开发几乎不用
二、对比总结

三、实际项目中的选择策略
1.默认使用构造函数注入
java
@Service
@RequiredArgsConstructor // Lombok 自动生成构造函数
public class StrategyService {
private final StrategyMapper strategyMapper;
private final VentureService ventureService;
}
2.可选依赖用 Setter 注入
java
@Service
public class ReportService {
private CacheService cacheService; // 缓存是可选的
@Autowired(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
public void generateReport() {
if (cacheService != null) {
cacheService.put(...); // 有缓存就用
}
}
}
3.循环依赖时配合 @Lazy
java
@Service
@RequiredArgsConstructor
public class ServiceA {
private final ServiceB serviceB;
}
@Service
@RequiredArgsConstructor
public class ServiceB {
@Lazy // 打破循环
private final ServiceA serviceA;
}
4.避免字段注入的原因
java
// ❌ 坏味道:依赖太多却看不出来
@Service
public class BadExample {
@Autowired private Service1 s1;
@Autowired private Service2 s2;
@Autowired private Service3 s3;
@Autowired private Service4 s4;
@Autowired private Service5 s5;
// ... 违反了单一职责原则
}
// ✅ 好实践:构造函数参数过多提示需要重构
@Service
@RequiredArgsConstructor
public class GoodExample {
private final Service1 s1;
private final Service2 s2;
// 如果超过 7 个参数,考虑拆分类
}
四、Spring 注入底层原理
java
// Spring 容器启动时的处理流程
1. 扫描 @Component/@Service 等注解,注册 BeanDefinition
2. 实例化 Bean(调用构造函数或无参构造)
3. 属性填充(populateBean)
- 如果是构造函数注入:在第 2 步完成
- 如果是字段/Setter 注入:在这一步通过反射赋值
4. 初始化(@PostConstruct、InitializingBean)
5. 放入单例池(singletonObjects)
五、最佳实践建议
1.统一团队规范
yaml
# .editorconfig 或团队文档
spring-injection-style: constructor # 强制使用构造函数注入
2.IDE 配置检查
IntelliJ IDEA:Settings → Editor → Inspections → Spring → Spring Core → Code
启用 "Autowiring for bean class" 警告字段注入