# 依赖注入 (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

问题: 如果想改为数据库记录日志,需要修改所有使用 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

好处:

  • 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

# 注入服务

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

# 🏗️ 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

# 程序集扫描扩展

// 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

# 框架模块的依赖注入

每个 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

# 使用示例

// 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

# 💡 最佳实践

# 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. 依赖抽象,不依赖实现

// ✅ 推荐
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

# 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

# 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

# 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

# 🔍 常见问题

# Q1: 服务无法注入?

检查清单:

  1. ✅ 服务是否已注册到容器?
  2. ✅ 接口和实现类型是否匹配?
  3. ✅ 是否存在循环依赖?
  4. ✅ 程序集是否被正确扫描?

# 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

# 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

# 📚 相关文档


# 🔗 参考链接


最后更新: 2024-09-20