Spring 中的 DI 是什么?
- 标签:后端、Spring
🏛 业务架构师
DI(依赖注入)是 IOC 的实现方式:对象不自己创建依赖,而是由容器在装配时"注入"(构造器/Setter/字段三种形式)。
业务价值我讲三笔账:
- 代码账:依赖显式化。构造器注入让类的依赖一眼可见——看构造器签名就知道这个服务依赖谁。依赖图=架构图:订单 Service 构造器里出现推荐客户端,CR 时立刻质疑"下单为什么依赖推荐"——越界依赖在编译期就被看见,而不是埋在方法体里某行 new 出来;
- 测试账:可测性即迭代速度。依赖可替换(mock 注入),单测不需要起数据库、不需要真实支付渠道——单测跑得快的团队才敢频繁重构,业务迭代速度的底座在这里;
- 运营账:装配即策略。同一接口注入不同实现(多租户差异、新旧引擎灰度切换),部署期决定行为,代码不动——写死 new 的系统没有这种运营弹性。
我的三条实践规约:
- 构造器注入优先:依赖不可隐藏、循环依赖启动期暴露(这是好事——逼你重构而不是运行期诡异行为)、天然可测;字段注入只为存量代码保留;
- 面向接口注入:注入 Map<String, PayService> 实现策略路由——依赖倒置让核心业务不依赖细节;
- 警惕"容器万能":工具类、纯函数对象别塞容器——IOC 管理有生命周期的协作对象,滥用是把容器当全局变量用。
一句话:DI 的本质是把"依赖关系"从代码细节提升为架构资产——可看见、可评审、可替换、可测试,四项全是要害。
🏗 系统架构师
DI(依赖注入)是 IOC 的实现方式:对象不自己创建依赖,而是由容器在装配时"注入"进来。要讲清三层:
注入的三种方式(我按推荐度排):
- 构造器注入(首选):依赖显式声明、不可变(final)、依赖不全直接启动失败(fail-fast)、单测直接 new;Spring 官方推荐;
- Setter 注入:可选依赖、可重配置场景;
- 字段注入(@Autowired 直接打字段):代码最少但掩盖依赖关系(类能偷偷依赖一堆)、没法 final、离开容器无法测试——我团队规范里禁用。
DI 的价值(架构视角):
- 面向接口装配:依赖以接口声明,实现由容器按配置/环境替换(数据源切多环境、mock 替换)——依赖倒置原则的落地;
- 对象图声明式管理:复杂依赖关系交给容器拓扑排序(这也是循环依赖暴露的原因——图成环);
- 统一生命周期与增强点:因为容器造对象,才能统一做作用域管理、AOP 织入、初始化回调。
与 IOC 的关系一句话:IOC 是思想(控制反转:装配权从程序移交给容器),DI 是手段(依赖被"推进来"而不是"拉出去"——Service Locator 是另一种手段但有隐藏依赖问题)。
实践细节:多实现用 @Qualifier/主从标记(@Primary);循环依赖优先重构而不是 @Lazy 绕过。
💻 全栈开发专家
DI(依赖注入)是 IOC 的具体实现方式:对象不自己创建/查找依赖,而是由容器在装配时"从外面注入"。
三种注入方式与我的偏好:
java
// 1. 构造器注入(官方推荐,我的默认选择)
@Service
public class OrderService {
private final OrderRepository repo; // final 不可变
public OrderService(OrderRepository repo) { this.repo = repo; }
}
// 2. Setter 注入:可选依赖、可重新配置的场景
@Autowired public void setRepo(OrderRepository repo) { ... }
// 3. 字段注入(@Autowired 直接标字段):看着省事,实际最差
@Autowired private OrderRepository repo;为什么我坚持构造器注入:
- 依赖显式——构造器参数一长说明类职责过多,逼你重构(字段注入会掩盖这个问题);
- 不可变(final)+ 对象构造完即可用,不存在"半初始化"状态;
- 脱离容器也能用——单测直接
new OrderService(mockRepo),不需要反射魔法; - 循环依赖在构造器注入下启动即失败——早失败比运行时埋雷好。
注解辨析:@Autowired(Spring,byType,配合 @Qualifier);@Resource(JSR-250,byName);@Inject(JSR-330)。构造器注入时 Spring 4.3+ 单构造器可省注解。
一句话:IOC 是思想(控制权反转给容器),DI 是手法(依赖塞进来),@Autowired 只是注解皮囊——选哪种注入方式才是工程判断的体现。