DI 容器如何解析对象(内部机制)
DI 容器如何解析对象(内部机制)
DI 容器是如何解析对象的?
你将会明白:
- 为什么构造函数能自动注入?
- 为什么能注入接口?
- 为什么能处理不同生命周期?
- 为什么会发生循环依赖?
- 为什么很多类你没注册也能创建出来?
我会用 代码示例 + 手绘式流程(文字版) + 武侠比喻 让你彻底理解 DI 的“脑子是怎么运作的”。
一、核心思想:DI 容器是一个“对象工厂”
DI 容器本质上就是:
类型/接口 → 创建实例的方法比如:
IUserService → new UserService(...)
ILogger → new Logger(...)它维护一张类型构造方案表,你注册的时候,就是在这张表里填一行。
二、底层原理
容器创建一个对象时:
1)找到该类型的“构造函数”
例如:
public UserService(IUserRepository repo, ILogger logger)容器会把构造函数参数拿出来:
需要:IUserRepository
需要:ILogger2)逐个解析构造函数的参数
容器遇到依赖:
IUserRepository就会查注册表:
IUserRepository → UserRepository然后容器会说:
我得先创建 UserRepository 才能创建 UserService。
于是它递归解析 UserRepository 的构造函数。
3)递归解析所有依赖直到最底层
类似:
UserService
→ IUserRepository → UserRepository
→ IFreeSql → FreeSql
→ (到底了,不再有依赖)
→ ILogger → Logger递归结束后,容器会:
- 从最底层开始往上“组装对象”
- 最终生成 UserService 实例
三、容器的内部过程
容器在解析一个对象时会做几件事:
① 查找注册表(注册信息表)
比如 ASP.NET Core 的依赖表长这样(简化):
IUserService → UserService (Scoped)
ILogger → Logger (Singleton)
IUserRepository → UserRepository (Transient)容器会用:
descriptor = lookup(type);找出你注册的服务。
② 根据生命周期决定是否创建新实例
生命周期策略:
生命周期规则Singleton整个应用只创建 1 个实例Scoped每个请求创建 1 个实例Transient每次解析都新建
容器内部大概这样:
if (descriptor.Lifetime == Singleton)
return GetOrCreateSingleton(descriptor);
if (descriptor.Lifetime == Scoped)
return GetOrCreateScoped(descriptor);
if (descriptor.Lifetime == Transient)
return CreateNewInstance(descriptor);③ 选构造函数(一般选最长的)
容器会通过反射:
var constructor = type
.GetConstructors()
.OrderByDescending(c => c.GetParameters().Length)
.First();ASP.NET Core 默认选择参数最多的构造函数。
④ 逐个解析构造函数参数(递归解析)
foreach (var param in constructor.Params)
{
var dependency = Resolve(param.Type);
collectedParams.Add(dependency);
}⑤ 调用构造函数创建实例
反射调用:
var instance = Activator.CreateInstance(type, collectedParams);这就是 DI 容器的底层机制。
四、武侠版理解
你把 DI 容器想成:
天下第一铸剑坊(DI 容器)
负责打造各路高手(对象实例)。
你提交一个需求(构造函数):
我要炼制一个「UserService」高手,所需材料:IUserRepository、ILogger。
铸剑坊说:
好,我先铸 IUserRepository。
但它需要 IFreeSql,我得先铸 IFreeSql…
于是:
UserService
↳ UserRepository
↳ FreeSql
(无依赖,开始铸造)
↲ UserRepository 完成
↳ Logger
↲ UserService 完成这就是 递归创建对象。
五、DI 容器为什么会检测到循环依赖?
因为:
UserService → OrderService → UserService容器在解析的过程中会维护一个“当前正在解析的类型栈”。
像这样:
正在解析:UserService
→ 解析 OrderService
→ 再解析 UserService ???容器发现:
我还没造完第一个 UserService,你又要造第二个 UserService,这不就循环了吗?
于是:
抛异常:Constructor cycle detected
六、DI 容器为什么能注入接口?
因为注册表告诉容器:
IUserService → UserService所以当构造函数需要 IUserService 时:
Resolve(IUserService) → new UserService(...)容器永远能找到它的实现类。
七、为什么很多类型不注册也能创建出来?
比如:
public class HomeController
{
public HomeController(ILogger logger)
}你没注册 HomeController,但容器仍能创建。
原因:
容器只要求:构造函数中的参数必须可解析。
类本身是否注册无所谓。
像 ASP.NET Core 的 Controller,就是容器动态创建的。
八、总结
- DI 容器是一个递归对象工厂。
- 根据你注册的映射表(接口 → 实现类)决定怎么创建。
- 根据生命周期决定实例是否复用。
- 通过反射找到构造函数,解析其中的依赖。
- 不断递归解析依赖树,因此可能出现循环依赖。
- 所有对象最终都是通过 Activator.CreateInstance 动态创建出来的。