下面我给你一个真正工程师能听懂的大白话解释,把:
- Service Locator vs DI 的本质区别
- DI 容器到底是怎么知道“给谁注入什么依赖”的
- 为什么 Service Locator 被认为是“伪 IoC”
- Spring / Koin / Dagger 是怎么做依赖匹配的
讲得非常清晰。
🧩 一、Service Locator vs DI —— 最通俗的大白话区别
🎯 一句话总结
Service Locator:对象主动去“找”依赖
DI(依赖注入):对象被动“被送来”依赖
再白一点:
Service Locator = 你自己去外卖平台点餐
DI = 外卖小哥把饭送到你手里
🧱 二、Service Locator(服务定位器)是什么?
Service Locator 的写法一般是这样:
val engine = ServiceLocator.get<Engine>()
val car = Car(engine)特点:
- 对象自己调用 ServiceLocator 去拿依赖
- 依赖查找逻辑写在业务代码里
- 业务代码知道“依赖从哪里来”
缺点:
- 隐式依赖(看不到构造函数参数)
- 难测试(你必须 mock ServiceLocator)
- 耦合度高(业务代码依赖容器)
本质:
依赖查找(Dependency Lookup)
🧱 三、DI(依赖注入)是什么?
DI 的写法是:
class Car(val engine: Engine)依赖由外部注入:
val car = Car(engine = GasEngine())或者由框架注入:
@Autowired
lateinit var engine: Engine特点:
- 对象不关心依赖从哪里来
- 依赖由容器负责创建和注入
- 业务代码只依赖接口,不依赖容器
本质:
依赖注入(Dependency Injection)
🎯 四、核心区别(工程师视角)
| 项目 | Service Locator | DI(依赖注入) |
|---|---|---|
| 谁负责获取依赖 | 对象自己 | 容器负责 |
| 依赖关系 | 隐式 | 显式(构造函数参数) |
| 可测试性 | 差(要 mock 容器) | 好(直接传 mock) |
| 耦合度 | 高(依赖容器) | 低(只依赖接口) |
| 依赖方向 | 对象 → 容器 | 容器 → 对象 |
| 是否符合 IoC | 不完全 | 完全 |
一句话:
Service Locator 是“主动找”,DI 是“被动给”。
🧠 五、DI 容器是如何知道“给哪个类注入哪个依赖”的?
这是你最关心的部分,我分三种主流 DI 框架讲:
1)Spring(运行时 DI)——通过反射 + 类型匹配
Spring 的流程:
✔ 第一步:扫描 Bean
Spring 启动时扫描所有:
@Component@Service@Repository@Bean
并把它们注册到 BeanFactory。
✔ 第二步:分析依赖(反射)
Spring 通过反射查看类的:
- 构造函数参数类型
- 字段类型(@Autowired)
- setter 方法参数类型
例如:
@Autowired
Engine engine;Spring 会看到:
- Car 需要一个 Engine 类型的依赖
- 容器里有 GasEngine 和 ElectricEngine
- 如果只有一个实现 → 注入它
- 如果多个 → 需要 @Qualifier 指定
✔ 第三步:实例化并注入
Spring 创建 Engine → 再创建 Car → 注入 Engine。
2)Koin(运行时 DI)——通过 DSL 注册 + 类型匹配
Koin 的流程:
✔ 第一步:你注册依赖
module {
single<Engine> { GasEngine() }
}✔ 第二步:Koin 记录“Engine → GasEngine”
✔ 第三步:构造函数注入时匹配类型
class Car(val engine: Engine)Koin 会:
- 反射 Car 的构造函数
- 看到需要 Engine
- 找到你注册的 Engine 实现
- 注入进去
3)Dagger/Hilt(编译时 DI)——通过注解处理器生成代码
Dagger 不用反射,它在编译期做:
✔ 第一步:扫描 @Inject 构造函数
class Car @Inject constructor(val engine: Engine)✔ 第二步:扫描 @Provides / @Binds
@Provides fun provideEngine(): Engine = GasEngine()✔ 第三步:生成依赖图 + 生成工厂类
编译器生成类似:
public class Car_Factory {
public Car get() {
return new Car(new GasEngine());
}
}所以 Dagger 是:
编译时就知道依赖关系,不用反射,性能极高。
🎯 六、DI 容器是如何“知道”依赖关系的?(最终总结)
DI 容器通过以下方式知道“给谁注入什么”:
- 扫描类的构造函数 / 字段 / setter
- 读取类型信息(Engine、Repository、Service 等)
- 在容器中查找对应类型的实现
- 实例化依赖
- 注入到目标对象
不同框架的实现方式不同:
| 框架 | 注入时机 | 依赖匹配方式 |
|---|---|---|
| Spring | 运行时 | 反射 + 类型匹配 |
| Koin | 运行时 | DSL 注册 + 反射 |
| Dagger/Hilt | 编译时 | 注解处理器生成代码 |
🏁 最终总结(最精炼版本)
✔ Service Locator vs DI
- Service Locator:对象主动找依赖(耦合高)
- DI:容器主动注入依赖(耦合低)
✔ DI 如何知道注入什么?
- 通过类型匹配
- 通过构造函数参数
- 通过注解(@Inject / @Autowired)
- 通过容器注册信息(module、@Bean)
✔ 不同框架的差异
- Spring/Koin:运行时反射
- Dagger/Hilt:编译时生成代码
如果你愿意,我还能给你:
🔥 一张“Service Locator vs DI 对比图”
🔥 一段“为什么 Service Locator 被认为是反模式”
🔥 一段“如何从 Service Locator 重构到 DI 的步骤”
你想继续深入哪一块?