.NET中依赖注入(DI)的具体工作机制是什么?
核心逻辑总览
ASP.NET Core自带的DI容器(基于IServiceProvider体系),处理构造函数注入的核心是解析依赖树+按依赖顺序实例化,整个流程分为注册、解析两个关键阶段。
1. 注册阶段:构建服务映射表
在项目启动时(比如Program.cs的ConfigureServices块),你通过AddScoped/AddSingleton/AddTransient注册的服务,会被容器转换成**服务描述符(ServiceDescriptor)**集合,每个描述符包含:
- 服务抽象类型(如
IUserService) - 具体实现类型(如
UserService) - 生命周期(单例/作用域/瞬时)
- 可选的自定义实例工厂
容器会把这些描述符整理成高效的查找结构,方便后续快速定位服务。
2. 解析阶段:实例创建与依赖链处理
当容器收到实例请求(比如控制器构造函数注入IUserService),会按以下步骤执行:
步骤1:定位服务描述符
根据请求的服务类型,从映射表中找到对应的实现类型或工厂方法。如果找不到匹配项,直接抛出InvalidOperationException(除非注册时标记为可选服务)。
步骤2:检查实例缓存
根据服务生命周期判断是否复用已有实例:
- 单例:如果已创建过实例,直接返回缓存对象,不再重复创建
- 作用域:在当前作用域(比如HTTP请求上下文)的缓存中查找,存在则返回,否则创建新实例并缓存到当前作用域
- 瞬时:每次请求都创建全新实例,不做缓存
步骤3:递归解析构造函数依赖
容器会先分析目标实现类的构造函数:
- 默认优先选择参数数量最多、且所有参数都能被容器解析的构造函数;如果有多个符合条件的构造函数,会抛出异常
- 对构造函数的每个参数,递归执行「定位描述符→检查缓存→解析参数依赖」的流程,形成完整的依赖链
举个实际例子:
假设你有以下服务链:
public class UserService : IUserService { private readonly ILogger<UserService> _logger; private readonly IRepository _repo; public UserService(ILogger<UserService> logger, IRepository repo) { _logger = logger; _repo = repo; } } public class Repository : IRepository { private readonly AppDbContext _dbContext; public Repository(AppDbContext dbContext) { _dbContext = dbContext; } }
当解析IUserService时,容器的执行顺序是:
- 找到
UserService的构造函数,需要ILogger<UserService>和IRepository - 先解析
ILogger<UserService>:容器内置了日志服务,直接返回对应实例 - 再解析
IRepository:找到Repository的构造函数,需要AppDbContext - 解析
AppDbContext:根据注册的DbContext配置创建实例 - 创建
Repository实例,传入AppDbContext - 创建
UserService实例,传入ILogger和Repository - 最后根据
UserService的生命周期,决定是否缓存后返回
步骤4:实例初始化(可选)
如果服务实现了IServiceProvider相关接口(比如IServiceScopeFactory),容器会在实例创建后自动注入这些接口;不过原生DI默认只支持构造函数注入,属性/方法注入需要手动配置。
3. 循环依赖的处理
如果依赖链出现闭环(比如A依赖B,B又依赖A),原生DI会直接抛出InvalidOperationException,因为它无法在无延迟加载的情况下创建循环依赖的实例。常见解决办法:
- 重构代码,拆解循环依赖
- 使用
Lazy<T>包装依赖,实现延迟加载 - 改用第三方DI容器(如Autofac),它们支持循环依赖的处理
4. 定制DI的关键点
如果需要调整DI行为,可以从这些方向入手:
- 自定义
IServiceProvider或IServiceScopeFactory,替换默认的实例创建逻辑 - 利用
ServiceDescriptor的工厂方法,自定义实例创建流程(比如AddSingleton(sp => new MyService(sp.GetRequiredService<IDependency>()))) - 实现
IConfigureOptions接口,动态调整服务配置 - 集成第三方DI容器,获得更灵活的构造函数选择、拦截器、循环依赖处理等功能
内容的提问来源于stack exchange,提问作者Venugopal M

