目录

DI 容器如何解析对象(内部机制)

DI 容器如何解析对象(内部机制)

DI 容器是如何解析对象的?

你将会明白:

  • 为什么构造函数能自动注入?
  • 为什么能注入接口?
  • 为什么能处理不同生命周期?
  • 为什么会发生循环依赖?
  • 为什么很多类你没注册也能创建出来?

我会用 代码示例 + 手绘式流程(文字版) + 武侠比喻 让你彻底理解 DI 的“脑子是怎么运作的”。

一、核心思想:DI 容器是一个“对象工厂”

DI 容器本质上就是:

类型/接口    →    创建实例的方法

比如:

IUserService → new UserService(...)
ILogger      → new Logger(...)

它维护一张类型构造方案表,你注册的时候,就是在这张表里填一行。

二、底层原理

容器创建一个对象时:

1)找到该类型的“构造函数”

例如:

public UserService(IUserRepository repo, ILogger logger)

容器会把构造函数参数拿出来:

需要:IUserRepository
需要:ILogger

2)逐个解析构造函数的参数

容器遇到依赖:

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,就是容器动态创建的。

八、总结

  1. DI 容器是一个递归对象工厂。
  2. 根据你注册的映射表(接口 → 实现类)决定怎么创建。
  3. 根据生命周期决定实例是否复用。
  4. 通过反射找到构造函数,解析其中的依赖。
  5. 不断递归解析依赖树,因此可能出现循环依赖。
  6. 所有对象最终都是通过 Activator.CreateInstance 动态创建出来的。