依赖注入生命周期
ASP.NET Core依赖注入生命周期详解:Scoped、Transient、Singleton。
概述
ASP.NET Core 的依赖注入(DI)容器支持三种服务生命周期:Singleton、Scoped 和 Transient。正确选择生命周期是构建可维护、高性能应用的关键。错误的生命周期配置可能导致数据泄漏、并发 Bug 或性能问题。
基础概念
三种生命周期
// 单例:整个应用生命周期内只有一个实例
services.AddSingleton<IDatabase, Database>();
// 作用域:每个 HTTP 请求内共享一个实例
services.AddScoped<IUserService, UserService>();
// 瞬态:每次注入都创建新实例
services.AddTransient<ILogger, Logger>();
生命周期对比
| 生命周期 | 创建时机 | 同一请求内 | 不同请求间 | 适用场景 |
|---|---|---|---|---|
| Singleton | 首次请求时 | 同一实例 | 同一实例 | 配置、缓存、数据库连接池 |
| Scoped | 每次请求时 | 同一实例 | 不同实例 | 请求上下文、数据库上下文 |
| Transient | 每次注入时 | 不同实例 | 不同实例 | 轻量级无状态服务 |
快速上手
注册服务
// 在 Program.cs 中注册服务
var builder = WebApplication.CreateBuilder(args);
// 单例服务
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddSingleton(new AppConfig { MaxRetries = 3 });
// 作用域服务
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddDbContext<AppDbContext>(); // EF Core 默认是 Scoped
// 瞬态服务
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
var app = builder.Build();
构造器注入
public class OrderController : ControllerBase {
private readonly IOrderService _orderService;
private readonly ICacheService _cacheService;
private readonly ILogger<OrderController> _logger;
// 构造器注入(推荐方式)
public OrderController(
IOrderService orderService,
ICacheService cacheService,
ILogger<OrderController> logger) {
_orderService = orderService;
_cacheService = cacheService;
_logger = logger;
}
}
详细用法
Captive Dependency 陷阱
// 错误:Singleton 依赖 Scoped 服务
// Singleton 服务只创建一次,其依赖的 Scoped 服务也只创建一次
// Scoped 服务被"囚禁"在 Singleton 中,变成单例行为
services.AddSingleton<ISingletonService, SingletonService>();
// SingletonService 构造器中注入了 Scoped 服务
// 依赖方向规则:
// Singleton 只能依赖 Singleton
// Scoped 可以依赖 Scoped 或 Singleton
// Transient 可以依赖任意生命周期
// 解决方案:使用 IServiceProvider 延迟解析
public class SingletonService {
private readonly IServiceProvider _serviceProvider;
public SingletonService(IServiceProvider serviceProvider) {
_serviceProvider = serviceProvider;
}
public void DoWork() {
// 每次创建新的作用域来解析 Scoped 服务
using var scope = _serviceProvider.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
scopedService.Process();
}
}
释放模式(IDisposable)
// 容器会自动释放实现了 IDisposable 的服务
// Singleton:应用关闭时释放
// Scoped:请求结束时释放
// Transient:默认不跟踪,除非从根容器解析
// 正确做法:Scoped 服务实现 IDisposable
public class AppDbContext : DbContext, IDisposable {
// 请求结束时自动调用 Dispose
}
// 错误:从根容器解析 Transient+IDisposable 会造成内存泄漏
// var service = app.Services.GetRequiredService<ITransientDisposable>();
// 解决:从作用域解析
using var scope = app.Services.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<ITransientDisposable>();
多实现注册
// 注册多个实现
services.AddSingleton<IEventHandler, EmailHandler>();
services.AddSingleton<IEventHandler, SmsHandler>();
services.AddSingleton<IEventHandler, PushHandler>();
// 注入 IEnumerable 获取所有实现
public class EventDispatcher {
private readonly IEnumerable<IEventHandler> _handlers;
public EventDispatcher(IEnumerable<IEventHandler> handlers) {
_handlers = handlers;
}
public async Task DispatchAsync(Event evt) {
foreach (var handler in _handlers) {
await handler.HandleAsync(evt);
}
}
}
// 使用 Keyed Services(.NET 8+)
services.AddKeyedSingleton<ICache, RedisCache>("redis");
services.AddKeyedSingleton<ICache, MemoryCache>("memory");
public class MyService {
public MyService([FromKeyedServices("redis")] ICache cache) { }
}
常见场景
请求上下文服务
// 使用 Scoped 存储请求上下文
public class RequestContext {
public string UserId { get; set; }
public string TraceId { get; set; }
public string ClientIp { get; set; }
}
// 中间件中设置上下文
app.Use(async (context, next) => {
var requestContext = context.RequestServices.GetRequiredService<RequestContext>();
requestContext.UserId = context.User.FindFirst("sub")?.Value;
requestContext.TraceId = context.TraceIdentifier;
requestContext.ClientIp = context.Connection.RemoteIpAddress?.ToString();
await next();
});
// 在任意服务中注入使用
public class AuditService {
private readonly RequestContext _requestContext;
public AuditService(RequestContext requestContext) {
_requestContext = requestContext;
}
public void Log(string action) {
Console.WriteLine($"[{_requestContext.TraceId}] 用户 {_requestContext.UserId} {action}");
}
}
工厂模式注册
// 使用工厂方法注册服务
services.AddSingleton<ISmsProvider>(sp => {
var config = sp.GetRequiredService<IConfiguration>();
var provider = config["Sms:Provider"];
return provider switch {
"aliyun" => new AliyunSmsProvider(config["Sms:AccessKey"]),
"tencent" => new TencentSmsProvider(config["Sms:SecretId"]),
_ => throw new InvalidOperationException($"未知的短信提供商: {provider}")
};
});
注意事项
- Singleton 服务必须是线程安全的,因为多个请求会并发访问同一实例
- 不要从根容器解析 Scoped 服务,会导致 Captive Dependency
- Transient + IDisposable 从根容器解析会造成内存泄漏
- 构造器注入是推荐方式,避免使用 [FromServices] 属性注入
- 避免在服务构造器中执行耗时操作,会影响启动性能
- 使用 ValidateScopes = true 检测生命周期违规(开发环境默认开启)
进阶用法
自动注册程序集
// 扫描程序集自动注册服务
var assembly = typeof(Program).Assembly;
var serviceTypes = assembly.GetTypes()
.Where(t => t.Name.EndsWith("Service") && !t.IsAbstract && !t.IsInterface);
foreach (var type in serviceTypes) {
var interfaceType = type.GetInterface($"I{type.Name}");
if (interfaceType != null) {
services.AddScoped(interfaceType, type);
}
}
// 使用 Scrutor 库简化
services.Scan(scan => scan
.FromAssemblyOf<Program>()
.AddClasses(classes => classes.Where(c => c.Name.EndsWith("Service")))
.AsImplementedInterfaces()
.WithScopedLifetime());
替代 DI 容器
// 使用 Autofac 替换默认容器
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(container => {
container.RegisterType<UserService>().As<IUserService>().InstancePerLifetimeScope();
container.RegisterType<EmailService>().As<IEmailService>().SingleInstance();
});