# 依赖注入 (DI)
最后更新: 2024-09-20
# 📚 核心概念
# 依赖注入 (DI) vs 控制反转 (IoC)
很多人将依赖注入和控制反转混为一谈,其实它们是不同的概念:
| 概念 | 全称 | 说明 |
|---|---|---|
| IoC | Inversion of Control(控制反转) | 面向对象编程的设计原则 |
| DI | Dependency Injection(依赖注入) | IoC 的实现方式 |
简单理解:
- IoC 是思想:将对象的创建和控制权交给外部
- DI 是实践:通过构造函数等方式注入依赖
# 🎯 为什么需要依赖注入?
# 问题场景:日志记录器
假设要在系统中记录日志,最初决定用文本文件记录:
// 文本日志记录器
public class TextLogger
{
public void Debug(string msg)
{
// 写入文本文件
}
}
// 业务类
public class ProductService
{
private readonly TextLogger _logger = new TextLogger();
public void CreateProduct()
{
// 创建产品逻辑
_logger.Debug("创建产品");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
问题: 如果想改为数据库记录日志,需要修改所有使用 TextLogger 的地方!
# 解决方案:接口 + 依赖注入
// 1. 定义接口
public interface ILogger
{
void Debug(string msg);
}
// 2. 实现接口
public class TextLogger : ILogger
{
public void Debug(string msg) => Console.WriteLine($"[Text] {msg}");
}
public class DatabaseLogger : ILogger
{
public void Debug(string msg) => Console.WriteLine($"[DB] {msg}");
}
// 3. 依赖注入
public class ProductService
{
private readonly ILogger _logger;
// 构造函数注入
public ProductService(ILogger logger)
{
_logger = logger;
}
public void CreateProduct()
{
// 业务逻辑
_logger.Debug("创建产品");
}
}
// 4. 使用时注入
var logger = new DatabaseLogger();
var service = new ProductService(logger);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
好处:
- ✅
ProductService只依赖接口,不关心具体实现 - ✅ 切换实现无需修改业务代码
- ✅ 便于单元测试(可以注入 Mock 对象)
# 🔧 .NET Core 依赖注入
# 服务生命周期
.NET Core 提供三种服务生命周期:
| 生命周期 | 方法 | 说明 | 适用场景 |
|---|---|---|---|
| Singleton | AddSingleton | 单例,整个应用生命周期内只有一个实例 | 配置服务、缓存服务 |
| Scoped | AddScoped | 作用域内单例,Web 请求内同一个实例 | EF Core DbContext |
| Transient | AddTransient | 瞬时,每次请求都创建新实例 | 无状态服务 |
# 注册服务
// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);
// 1. 添加框架服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 2. 注册自定义服务
builder.Services.AddSingleton<ILogger, DatabaseLogger>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddTransient<IEmailService, EmailService>();
var app = builder.Build();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 注入服务
public class ProductController : ControllerBase
{
private readonly IProductService _productService;
private readonly ILogger _logger;
// 构造函数注入(推荐)
public ProductController(
IProductService productService,
ILogger logger)
{
_productService = productService;
_logger = logger;
}
[HttpGet("products")]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 🏗️ DarkM 框架的依赖注入
# 自动注册机制
DarkM 框架提供了自动注册服务的能力,通过特性标记实现:
// 1. 定义特性
[AttributeUsage(AttributeTargets.Class)]
public class SingletonAttribute : Attribute { }
// 2. 标记服务
[Singleton]
public class CacheService : ICacheService
{
// 单例服务
}
// 3. 自动扫描注册
// 框架会自动扫描包含 SingletonAttribute 的类并注册
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 程序集扫描扩展
// ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
/// <summary>
/// 从程序集自动注册包含 SingletonAttribute 特性的类
/// </summary>
public static IServiceCollection AddSingletonFromAssembly(
this IServiceCollection services,
Assembly assembly)
{
var types = assembly.GetTypes()
.Where(t => t.GetCustomAttributes(typeof(SingletonAttribute), false).Any());
foreach (var type in types)
{
var interfaceType = type.GetInterfaces().FirstOrDefault();
if (interfaceType != null)
{
services.AddSingleton(interfaceType, type);
}
}
return services;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 框架模块的依赖注入
每个 DarkM 模块都有独立的依赖注入配置:
// DarkM.Module.Admin.Web/AdminModule.cs
public class AdminModule : IModule
{
public void ConfigureServices(IServiceCollection services)
{
// 注册 Admin 模块服务
services.AddScoped<IAccountService, AccountService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IMenuService, MenuService>();
// 注册仓储
services.AddScoped<IAccountRepository, AccountRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用示例
// 1. 在 WebHost/Program.cs 中
var builder = WebApplication.CreateBuilder(args);
// 添加 DarkM 核心服务
builder.Services.AddDarkM();
// 添加 Admin 模块
builder.Services.AddDarkMModule<AdminModule>();
// 添加其他模块
builder.Services.AddDarkMModule<CommonModule>();
builder.Services.AddDarkMModule<QuartzModule>();
var app = builder.Build();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 💡 最佳实践
# 1. 优先使用构造函数注入
// ✅ 推荐:构造函数注入
public class ProductService
{
private readonly IRepository _repo;
public ProductService(IRepository repo)
{
_repo = repo;
}
}
// ⚠️ 避免:属性注入(除非特殊场景)
public class ProductService
{
[Inject]
public IRepository Repo { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 依赖抽象,不依赖实现
// ✅ 推荐
public class OrderService
{
private readonly IRepository<Order> _repo;
public OrderService(IRepository<Order> repo)
{
_repo = repo;
}
}
// ❌ 避免
public class OrderService
{
private readonly SqlServerRepository<Order> _repo;
public OrderService(SqlServerRepository<Order> repo)
{
_repo = repo;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. 避免服务定位器模式
// ❌ 避免:服务定位器
public class ProductService
{
public void Create()
{
var logger = ServiceLocator.Resolve<ILogger>();
logger.Debug("创建产品");
}
}
// ✅ 推荐:依赖注入
public class ProductService
{
private readonly ILogger _logger;
public ProductService(ILogger logger)
{
_logger = logger;
}
public void Create()
{
_logger.Debug("创建产品");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4. 避免循环依赖
// ❌ 错误:循环依赖
public class ServiceA
{
public ServiceA(ServiceB b) { }
}
public class ServiceB
{
public ServiceB(ServiceA a) { }
}
// ✅ 解决:提取公共接口
public interface ICommonService { }
public class ServiceA : ICommonService
{
public ServiceA(ICommonService common) { }
}
public class ServiceB
{
public ServiceB(ICommonService common) { }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 5. 单例服务避免注入 Scoped 服务
// ❌ 错误:单例服务注入 Scoped 服务
[Singleton]
public class CacheService
{
public CacheService(DbContext context) { } // DbContext 是 Scoped
}
// ✅ 解决:使用工厂或 IServiceProvider
[Singleton]
public class CacheService
{
private readonly IServiceProvider _serviceProvider;
public CacheService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void GetData()
{
using var scope = _serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<DbContext>();
// 使用 context
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 🔍 常见问题
# Q1: 服务无法注入?
检查清单:
- ✅ 服务是否已注册到容器?
- ✅ 接口和实现类型是否匹配?
- ✅ 是否存在循环依赖?
- ✅ 程序集是否被正确扫描?
# Q2: 如何注入多个实现?
// 注册多个实现
services.AddSingleton<IMessageHandler, EmailHandler>();
services.AddSingleton<IMessageHandler, SmsHandler>();
services.AddSingleton<IMessageHandler, WechatHandler>();
// 注入所有实现
public class NotificationService
{
private readonly IEnumerable<IMessageHandler> _handlers;
public NotificationService(IEnumerable<IMessageHandler> handlers)
{
_handlers = handlers;
}
public void Send(string message)
{
foreach (var handler in _handlers)
{
handler.Send(message);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Q3: 如何在静态类中使用 DI?
不推荐,但如果必须:
public static class StaticHelper
{
private static IServiceProvider _serviceProvider;
public static void Configure(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public static T GetService<T>()
{
return _serviceProvider.GetRequiredService<T>();
}
}
// Program.cs 中配置
StaticHelper.Configure(app.Services);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 📚 相关文档
# 🔗 参考链接
最后更新: 2024-09-20