依赖注入是什么
依赖注入把依赖的创建与使用分离:对象只声明自己需要什么,由外部(容器或组合根)把实例传进来。它是实现依赖倒置原则的常用手段,让高层模块依赖抽象接口而不是具体实现,从而可以替换实现、便于单元测试。依赖注入不等于必须使用容器,小型项目在启动处手工装配往往更直观、启动更快、依赖关系也更清晰。
基本信息
| 注入方式 | 构造器注入(推荐)、方法注入、属性注入 |
|---|---|
| 生命周期 | 单例、作用域、瞬态三种,混用不当会引发状态串扰 |
| 组合根 | 应用启动处集中装配依赖,其他位置不再 new 具体实现 |
| 常见容器 | Spring、.NET DI、Guice、Dagger,语言生态各有实现 |
| 与工厂区别 | 工厂负责创建,注入负责传递,实际项目常结合使用 |
构造器注入示例
构造器注入示例
class OrderService {
private final OrderRepository repo; # 只依赖接口,不关心实现
OrderService(OrderRepository repo) { # 依赖由外部传入
this.repo = repo;
}
}
# 组合根:new OrderService(new MysqlOrderRepository(ds))
# 单元测试:new OrderService(new InMemoryOrderRepository())生命周期选择
| 生命周期 | 适用对象 | 风险 |
|---|---|---|
| 单例 | 无状态服务、配置、连接池 | 持有请求级数据会串号 |
| 作用域 | 一次请求内的上下文对象 | 跨请求复用会读到旧数据 |
| 瞬态 | 轻量、状态独立的对象 | 频繁创建带来开销与内存压力 |
典型使用场景
- 单元测试:注入内存实现,测试无需启动数据库与网络
- 多环境适配:开发、测试、生产注入不同实现,代码不变
- 替换第三方依赖:以接口隔离供应商 SDK,便于更换
- 控制外部资源:把连接池、客户端等昂贵对象统一管理生命周期
- 分层解耦:业务层只依赖抽象,持久化实现可自由替换
- 条件装配:按配置决定注入哪个实现,避免散落的条件判断
判断注入是否用对的一个直观标准:业务类里是否还有 new 具体依赖的语句;如果还有,替换与测试都会立刻变难。
把容器当作全局服务定位器(到处从容器取依赖),依赖关系变得不可见,编译期无法发现缺失,测试也更难构造。