惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
C
Check Point Blog
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
SecWiki News
SecWiki News
有赞技术团队
有赞技术团队
Latest news
Latest news
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
G
GRAHAM CLULEY
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
腾讯CDC
P
Proofpoint News Feed
L
LINUX DO - 热门话题
C
Cisco Blogs
P
Palo Alto Networks Blog
Vercel News
Vercel News
P
Privacy International News Feed
爱范儿
爱范儿
Scott Helme
Scott Helme
L
Lohrmann on Cybersecurity
MyScale Blog
MyScale Blog
K
Kaspersky official blog
B
Blog RSS Feed
美团技术团队
Microsoft Security Blog
Microsoft Security Blog
O
OpenAI News
博客园 - 叶小钗
量子位
T
Tenable Blog
C
Cybersecurity and Infrastructure Security Agency CISA
J
Java Code Geeks
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
F
Fortinet All Blogs
N
News | PayPal Newsroom
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
N
News and Events Feed by Topic
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
Last Week in AI
Last Week in AI

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第五章:依赖注入与服务注册
我才是银古 · 2026-06-20 · via 博客园 - 我才是银古

第五章:依赖注入与服务注册

目录

  1. 依赖注入基础概念
  2. Furion依赖注入方式
  3. 手动注册服务
  4. 构造函数注入
  5. 属性注入
  6. 方法注入
  7. 命名服务与多实现
  8. Autofac集成
  9. 服务解析
  10. 生命周期管理详解
  11. 依赖注入最佳实践

1. 依赖注入基础概念

1.1 什么是依赖注入

依赖注入(Dependency Injection,简称DI)是控制反转(Inversion of Control,简称IoC)设计原则的一种具体实现方式。它的核心思想是:对象不再自己创建所依赖的组件,而是由外部容器来创建并注入

没有依赖注入的代码(紧耦合):

// ❌ 紧耦合:UserService直接创建依赖
public class UserService
{
    private readonly SqlServerUserRepository _repository;
    private readonly SmtpEmailService _emailService;

    public UserService()
    {
        // 直接在构造函数中创建依赖
        _repository = new SqlServerUserRepository("connection-string");
        _emailService = new SmtpEmailService("smtp.example.com");
    }

    public void CreateUser(string name)
    {
        _repository.Add(new User { Name = name });
        _emailService.Send("欢迎注册", name);
    }
}

使用依赖注入的代码(松耦合):

// ✅ 松耦合:依赖通过构造函数注入
public class UserService
{
    private readonly IUserRepository _repository;
    private readonly IEmailService _emailService;

    public UserService(IUserRepository repository, IEmailService emailService)
    {
        _repository = repository;
        _emailService = emailService;
    }

    public void CreateUser(string name)
    {
        _repository.Add(new User { Name = name });
        _emailService.Send("欢迎注册", name);
    }
}

1.2 IoC容器的工作原理

┌─────────────────────────────────────────────┐
│              IoC 容器                        │
│                                              │
│   注册阶段:                                  │
│   ┌───────────────────────────────────┐      │
│   │ IUserRepository → SqlServerRepo  │      │
│   │ IEmailService   → SmtpEmail      │      │
│   │ ILogger<T>      → ConsoleLogger  │      │
│   │ UserService     → UserService    │      │
│   └───────────────────────────────────┘      │
│                                              │
│   解析阶段:                                  │
│   请求 UserService                            │
│   ├── 发现需要 IUserRepository               │
│   │   └── 创建 SqlServerUserRepository       │
│   ├── 发现需要 IEmailService                 │
│   │   └── 创建 SmtpEmailService              │
│   └── 创建 UserService(注入上述依赖)        │
│                                              │
└─────────────────────────────────────────────┘

1.3 三种服务生命周期

ASP.NET Core和Furion支持三种服务生命周期:

生命周期 说明 适用场景 Furion接口
Transient(瞬时) 每次请求都创建新实例 轻量级无状态服务 ITransient
Scoped(作用域) 每个HTTP请求共享一个实例 数据库上下文、工作单元 IScoped
Singleton(单例) 整个应用程序生命周期共享一个实例 配置服务、缓存服务 ISingleton
HTTP请求1         HTTP请求2         HTTP请求3
│                 │                 │
├─Transient A1    ├─Transient A3    ├─Transient A5
├─Transient A2    ├─Transient A4    ├─Transient A6
│(每次不同)      │(每次不同)      │(每次不同)
│                 │                 │
├─Scoped B1       ├─Scoped B2       ├─Scoped B3
├─Scoped B1       ├─Scoped B2       ├─Scoped B3
│(同一请求相同)   │(同一请求相同)   │(同一请求相同)
│                 │                 │
├─Singleton C1    ├─Singleton C1    ├─Singleton C1
│(全局唯一)      │(全局唯一)      │(全局唯一)

2. Furion依赖注入方式

2.1 接口标记方式(推荐)

Furion最具特色的依赖注入方式是通过实现标记接口来自动注册服务,无需手动在Startup中配置:

// ======= 定义服务接口 =======
public interface IUserService
{
    Task<UserDto> GetByIdAsync(int id);
    Task<List<UserDto>> GetAllAsync();
    Task CreateAsync(CreateUserInput input);
}

// ======= 瞬时服务:实现ITransient =======
public class UserService : IUserService, ITransient
{
    private readonly ILogger<UserService> _logger;

    public UserService(ILogger<UserService> logger)
    {
        _logger = logger;
    }

    public async Task<UserDto> GetByIdAsync(int id)
    {
        _logger.LogInformation("获取用户:{Id}", id);
        return new UserDto { Id = id, Name = "示例用户" };
    }

    public async Task<List<UserDto>> GetAllAsync()
    {
        return new List<UserDto>();
    }

    public async Task CreateAsync(CreateUserInput input)
    {
        _logger.LogInformation("创建用户:{Name}", input.Name);
    }
}
// ======= 作用域服务:实现IScoped =======
public interface IOrderService
{
    Task<OrderDto> GetByIdAsync(int id);
}

public class OrderService : IOrderService, IScoped
{
    private readonly IRepository<Order> _repository;

    public OrderService(IRepository<Order> repository)
    {
        _repository = repository;
    }

    public async Task<OrderDto> GetByIdAsync(int id)
    {
        var order = await _repository.FindAsync(id);
        return order?.Adapt<OrderDto>();
    }
}
// ======= 单例服务:实现ISingleton =======
public interface ICacheService
{
    T Get<T>(string key);
    void Set<T>(string key, T value, TimeSpan? expiration = null);
    void Remove(string key);
}

public class MemoryCacheService : ICacheService, ISingleton
{
    private readonly ConcurrentDictionary<string, object> _cache = new();

    public T Get<T>(string key)
    {
        return _cache.TryGetValue(key, out var value) ? (T)value : default;
    }

    public void Set<T>(string key, T value, TimeSpan? expiration = null)
    {
        _cache[key] = value;
    }

    public void Remove(string key)
    {
        _cache.TryRemove(key, out _);
    }
}

2.2 自动注册的规则

Furion会自动扫描程序集中实现了ITransientIScopedISingleton接口的类,并按照以下规则注册:

场景 注册方式 示例
类实现了业务接口+标记接口 以业务接口为服务类型注册 IUserService → UserService
类只实现了标记接口 以自身类型注册 CacheService → CacheService
类实现多个业务接口+标记接口 每个业务接口分别注册 IReader → FileHandler, IWriter → FileHandler
// 场景1:接口+标记 → 注册为IUserService
public class UserService : IUserService, ITransient { }
// 等价于:services.AddTransient<IUserService, UserService>();

// 场景2:只有标记 → 注册为自身类型
public class HelperService : ITransient { }
// 等价于:services.AddTransient<HelperService>();

// 场景3:多接口 → 分别注册
public class FileHandler : IReader, IWriter, IScoped { }
// 等价于:
// services.AddScoped<IReader, FileHandler>();
// services.AddScoped<IWriter, FileHandler>();

2.3 排除自动注册

如果某些类不希望被自动注册,可以使用[SuppressSniffer]特性:

using Furion;

[SuppressSniffer]
public class InternalHelper : ITransient
{
    // 这个类不会被自动注册
}

3. 手动注册服务

3.1 在Startup中手动注册

虽然Furion提供了自动注册功能,但有时你仍然需要手动注册服务:

// Startup.cs 或 Program.cs
public void ConfigureServices(IServiceCollection services)
{
    // 瞬时服务
    services.AddTransient<IEmailService, SmtpEmailService>();

    // 作用域服务
    services.AddScoped<IUnitOfWork, UnitOfWork>();

    // 单例服务
    services.AddSingleton<IConfigService, ConfigService>();

    // 使用工厂方法注册
    services.AddTransient<IPaymentService>(sp =>
    {
        var config = sp.GetRequiredService<IConfiguration>();
        var gateway = config["Payment:Gateway"];

        return gateway switch
        {
            "Alipay" => new AlipayService(config),
            "WeChat" => new WeChatPayService(config),
            _ => throw new NotSupportedException($"不支持的支付网关:{gateway}")
        };
    });

    // 注册泛型服务
    services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

    // 注册多个实现
    services.AddTransient<INotifier, EmailNotifier>();
    services.AddTransient<INotifier, SmsNotifier>();
    services.AddTransient<INotifier, PushNotifier>();
}

3.2 条件注册

// 仅当服务未注册时才注册
services.TryAddTransient<IEmailService, SmtpEmailService>();
services.TryAddScoped<IUnitOfWork, UnitOfWork>();
services.TryAddSingleton<IConfigService, ConfigService>();

// 替换已注册的服务
services.Replace(ServiceDescriptor.Transient<IEmailService, MailgunService>());

3.3 批量注册

// 使用反射批量注册某个命名空间下的所有服务
var assembly = typeof(Startup).Assembly;
var serviceTypes = assembly.GetTypes()
    .Where(t => t.Namespace == "MyApp.Services" && t.IsClass && !t.IsAbstract);

foreach (var type in serviceTypes)
{
    var interfaceType = type.GetInterfaces().FirstOrDefault();
    if (interfaceType != null)
    {
        services.AddScoped(interfaceType, type);
    }
}

4. 构造函数注入

4.1 基本构造函数注入

构造函数注入是最常用和最推荐的依赖注入方式:

public class ArticleAppService : IDynamicApiController
{
    private readonly IRepository<Article> _articleRepo;
    private readonly IRepository<Category> _categoryRepo;
    private readonly ILogger<ArticleAppService> _logger;
    private readonly IMapper _mapper;
    private readonly ICacheService _cache;

    /// <summary>
    /// 构造函数注入多个依赖
    /// </summary>
    public ArticleAppService(
        IRepository<Article> articleRepo,
        IRepository<Category> categoryRepo,
        ILogger<ArticleAppService> logger,
        IMapper mapper,
        ICacheService cache)
    {
        _articleRepo = articleRepo;
        _categoryRepo = categoryRepo;
        _logger = logger;
        _mapper = mapper;
        _cache = cache;
    }

    public async Task<ArticleDto> GetByIdAsync(int id)
    {
        // 先查缓存
        var cacheKey = $"article:{id}";
        var cached = _cache.Get<ArticleDto>(cacheKey);
        if (cached != null) return cached;

        // 查数据库
        _logger.LogInformation("从数据库查询文章:{Id}", id);
        var article = await _articleRepo.FindAsync(id);
        var dto = _mapper.Map<ArticleDto>(article);

        // 写缓存
        _cache.Set(cacheKey, dto, TimeSpan.FromMinutes(10));

        return dto;
    }
}

4.2 主构造函数(C# 12+)

在C# 12及以上版本中,可以使用主构造函数语法简化代码:

// C# 12 主构造函数语法
public class UserAppService(
    IRepository<User> repository,
    ILogger<UserAppService> logger,
    IMapper mapper) : IDynamicApiController
{
    public async Task<List<UserDto>> GetAllAsync()
    {
        logger.LogInformation("查询所有用户");
        var users = await repository.AsQueryable().ToListAsync();
        return mapper.Map<List<UserDto>>(users);
    }

    public async Task<UserDto> GetByIdAsync(int id)
    {
        var user = await repository.FindAsync(id);
        return mapper.Map<UserDto>(user);
    }
}

4.3 可选依赖

如果某个依赖是可选的,可以将参数类型改为可空或提供默认值:

public class FlexibleService : ITransient
{
    private readonly ILogger<FlexibleService> _logger;
    private readonly ICacheService _cache;
    private readonly INotificationService _notification;

    public FlexibleService(
        ILogger<FlexibleService> logger,
        ICacheService cache = null,              // 可选依赖
        INotificationService notification = null) // 可选依赖
    {
        _logger = logger;
        _cache = cache;
        _notification = notification;
    }

    public void DoWork()
    {
        _logger.LogInformation("执行工作");

        // 安全使用可选依赖
        _cache?.Set("key", "value");
        _notification?.Send("工作完成");
    }
}

5. 属性注入

5.1 Furion属性注入

Furion虽然默认使用ASP.NET Core内置的DI容器(不原生支持属性注入),但在集成Autofac后可以支持属性注入。不过Furion也提供了一种简便的属性获取服务的方式:

public class PropertyInjectionService : IDynamicApiController
{
    // 通过App静态类在属性中获取服务
    private ILogger<PropertyInjectionService> Logger =>
        App.GetService<ILogger<PropertyInjectionService>>();

    private IRepository<User> UserRepo =>
        App.GetService<IRepository<User>>();

    public async Task<List<UserDto>> GetAllAsync()
    {
        Logger.LogInformation("使用属性方式获取服务");
        return await UserRepo.AsQueryable()
            .Select(u => new UserDto { Id = u.Id, Name = u.UserName })
            .ToListAsync();
    }
}

5.2 使用Autofac实现真正的属性注入

集成Autofac后,可以使用标准的属性注入语法:

// 需要安装Autofac包
// dotnet add package Autofac.Extensions.DependencyInjection

public class AutofacPropertyService
{
    // Autofac属性注入
    public ILogger<AutofacPropertyService> Logger { get; set; }
    public IUserRepository UserRepository { get; set; }

    public void DoWork()
    {
        Logger.LogInformation("Autofac属性注入");
        var users = UserRepository.GetAll();
    }
}

5.3 属性注入的注意事项

注意事项 说明
不推荐作为主要方式 构造函数注入更明确,更容易测试
循环依赖 属性注入可以解决某些循环依赖问题
延迟初始化 属性注入是延迟的,可能为null
可测试性 属性注入的可测试性较差

6. 方法注入

6.1 在Action方法中注入

ASP.NET Core支持在控制器的Action方法中通过[FromServices]特性注入服务:

using Microsoft.AspNetCore.Mvc;

public class ReportService : IDynamicApiController
{
    // 构造函数注入常用依赖
    private readonly ILogger<ReportService> _logger;

    public ReportService(ILogger<ReportService> logger)
    {
        _logger = logger;
    }

    // 方法注入:仅在特定方法中使用的依赖
    public async Task<ReportDto> GenerateReportAsync(
        [FromServices] IReportGenerator generator,
        [FromServices] IExcelExporter exporter,
        [FromQuery] ReportQueryInput input)
    {
        _logger.LogInformation("生成报表:{Type}", input.ReportType);

        var data = await generator.GenerateAsync(input);
        await exporter.ExportAsync(data);

        return data;
    }

    // 另一个方法不需要上述服务
    public List<string> GetReportTypes()
    {
        return new List<string> { "日报", "周报", "月报", "年报" };
    }
}

6.2 方法注入的优势

public class OptimizedService : IDynamicApiController
{
    // 只在构造函数中注入通用依赖
    private readonly ILogger<OptimizedService> _logger;

    public OptimizedService(ILogger<OptimizedService> logger)
    {
        _logger = logger;
    }

    // 导出Excel - 仅在此方法中需要IExcelExporter
    public async Task<IActionResult> ExportToExcelAsync(
        [FromServices] IExcelExporter exporter,
        [FromQuery] ExportInput input)
    {
        var bytes = await exporter.ExportAsync(input);
        return new FileContentResult(bytes, "application/vnd.ms-excel");
    }

    // 发送邮件 - 仅在此方法中需要IEmailService
    public async Task SendNotificationAsync(
        [FromServices] IEmailService emailService,
        string to, string subject, string body)
    {
        await emailService.SendAsync(to, subject, body);
    }

    // 普通方法 - 不需要额外的服务
    public string GetVersion() => "1.0.0";
}

6.3 方法注入 vs 构造函数注入

对比项 构造函数注入 方法注入
适用范围 多个方法共用的依赖 仅单个方法使用的依赖
性能 实例创建时注入 方法调用时注入
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐
可测试性 一般
代码清晰度 依赖关系一目了然 可能分散在各方法中

7. 命名服务与多实现

7.1 同一接口多个实现的问题

当一个接口有多个实现时,默认的DI容器会注入最后注册的实现:

// 接口定义
public interface IMessageSender
{
    Task SendAsync(string to, string content);
}

// 实现1:短信
public class SmsSender : IMessageSender, ITransient
{
    public Task SendAsync(string to, string content)
    {
        Console.WriteLine($"发送短信到 {to}: {content}");
        return Task.CompletedTask;
    }
}

// 实现2:邮件
public class EmailSender : IMessageSender, ITransient
{
    public Task SendAsync(string to, string content)
    {
        Console.WriteLine($"发送邮件到 {to}: {content}");
        return Task.CompletedTask;
    }
}

// 实现3:推送
public class PushSender : IMessageSender, ITransient
{
    public Task SendAsync(string to, string content)
    {
        Console.WriteLine($"发送推送到 {to}: {content}");
        return Task.CompletedTask;
    }
}

7.2 注入所有实现

public class NotificationService : IDynamicApiController
{
    private readonly IEnumerable<IMessageSender> _senders;

    // 注入所有IMessageSender的实现
    public NotificationService(IEnumerable<IMessageSender> senders)
    {
        _senders = senders;
    }

    /// <summary>
    /// 通过所有渠道发送通知
    /// </summary>
    public async Task SendToAllChannelsAsync(string to, string content)
    {
        foreach (var sender in _senders)
        {
            await sender.SendAsync(to, content);
        }
    }
}

7.3 命名服务(.NET 8+)

.NET 8引入了原生的Keyed Services(命名服务)支持:

// 注册命名服务
services.AddKeyedTransient<IMessageSender, SmsSender>("sms");
services.AddKeyedTransient<IMessageSender, EmailSender>("email");
services.AddKeyedTransient<IMessageSender, PushSender>("push");

// 使用命名服务
public class SmartNotificationService : IDynamicApiController
{
    private readonly IMessageSender _smsSender;
    private readonly IMessageSender _emailSender;

    public SmartNotificationService(
        [FromKeyedServices("sms")] IMessageSender smsSender,
        [FromKeyedServices("email")] IMessageSender emailSender)
    {
        _smsSender = smsSender;
        _emailSender = emailSender;
    }

    public async Task SendSmsAsync(string phone, string content)
    {
        await _smsSender.SendAsync(phone, content);
    }

    public async Task SendEmailAsync(string email, string content)
    {
        await _emailSender.SendAsync(email, content);
    }
}

7.4 使用工厂模式解决多实现

// 定义消息发送器工厂
public interface IMessageSenderFactory
{
    IMessageSender Create(string channel);
}

public class MessageSenderFactory : IMessageSenderFactory, ITransient
{
    private readonly IServiceProvider _serviceProvider;

    public MessageSenderFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IMessageSender Create(string channel)
    {
        return channel.ToLower() switch
        {
            "sms" => _serviceProvider.GetRequiredService<SmsSender>(),
            "email" => _serviceProvider.GetRequiredService<EmailSender>(),
            "push" => _serviceProvider.GetRequiredService<PushSender>(),
            _ => throw new ArgumentException($"不支持的消息渠道:{channel}")
        };
    }
}

// 使用工厂
public class MessageService : IDynamicApiController
{
    private readonly IMessageSenderFactory _factory;

    public MessageService(IMessageSenderFactory factory)
    {
        _factory = factory;
    }

    public async Task SendAsync(string channel, string to, string content)
    {
        var sender = _factory.Create(channel);
        await sender.SendAsync(to, content);
    }
}

8. Autofac集成

8.1 安装Autofac

dotnet add package Autofac.Extensions.DependencyInjection

8.2 配置Autofac

// Program.cs
using Autofac;
using Autofac.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// 使用Autofac替换默认的DI容器
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
    // 在这里使用Autofac的API注册服务
    containerBuilder.RegisterModule<AppAutofacModule>();
});

builder.Inject();

var app = builder.Build();
app.Run();

8.3 Autofac模块

using Autofac;
using System.Reflection;

namespace MyApp.Web.Core;

/// <summary>
/// Autofac服务注册模块
/// </summary>
public class AppAutofacModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // 按程序集批量注册
        var assembly = Assembly.Load("MyApp.Application");

        // 注册所有以Service结尾的类
        builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.Name.EndsWith("Service"))
            .AsImplementedInterfaces()
            .InstancePerDependency(); // 瞬时

        // 注册所有以Repository结尾的类
        builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.Name.EndsWith("Repository"))
            .AsImplementedInterfaces()
            .InstancePerLifetimeScope(); // 作用域

        // 注册单例
        builder.RegisterType<GlobalConfigService>()
            .As<IGlobalConfigService>()
            .SingleInstance();

        // 属性注入
        builder.RegisterType<AuditService>()
            .As<IAuditService>()
            .PropertiesAutowired() // 启用属性注入
            .InstancePerLifetimeScope();

        // 命名注册
        builder.RegisterType<SmsSender>()
            .Named<IMessageSender>("sms")
            .InstancePerDependency();

        builder.RegisterType<EmailSender>()
            .Named<IMessageSender>("email")
            .InstancePerDependency();
    }
}

8.4 Autofac高级特性

// 拦截器(AOP)
using Autofac.Extras.DynamicProxy;

// 定义拦截器
public class LoggingInterceptor : IInterceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    {
        _logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        _logger.LogInformation("调用方法:{Method}", invocation.Method.Name);
        var stopwatch = Stopwatch.StartNew();

        invocation.Proceed(); // 执行原方法

        stopwatch.Stop();
        _logger.LogInformation("方法 {Method} 执行耗时:{Elapsed}ms",
            invocation.Method.Name, stopwatch.ElapsedMilliseconds);
    }
}

// 注册带拦截器的服务
builder.RegisterType<OrderService>()
    .As<IOrderService>()
    .EnableInterfaceInterceptors() // 启用接口拦截
    .InterceptedBy(typeof(LoggingInterceptor))
    .InstancePerLifetimeScope();

9. 服务解析

9.1 通过App静态类解析

Furion提供了App静态类来在任何地方解析服务:

using Furion;

// 获取服务(可能返回null)
var userService = App.GetService<IUserService>();

// 获取必须的服务(不存在则抛异常)
var logger = App.GetRequiredService<ILogger<Program>>();

// 获取配置选项
var jwtOptions = App.GetOptions<JwtSettingsOptions>();

// 获取所有注册的实现
var senders = App.GetServices<IMessageSender>();

// 在静态方法中使用
public static class Helper
{
    public static void DoSomething()
    {
        var service = App.GetService<IMyService>();
        service?.Execute();
    }
}

9.2 通过IServiceProvider解析

public class ServiceResolverDemo : IDynamicApiController
{
    private readonly IServiceProvider _serviceProvider;

    public ServiceResolverDemo(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public string TestResolve()
    {
        // 解析单个服务
        var userService = _serviceProvider.GetService<IUserService>();
        var requiredService = _serviceProvider.GetRequiredService<ILogger<ServiceResolverDemo>>();

        // 创建作用域(解析Scoped服务时需要)
        using var scope = _serviceProvider.CreateScope();
        var scopedService = scope.ServiceProvider.GetService<IScopedService>();

        return "服务解析成功";
    }
}

9.3 在中间件中解析服务

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // 在Invoke方法中通过参数注入(推荐)
    public async Task InvokeAsync(
        HttpContext context,
        IUserService userService,
        ILogger<CustomMiddleware> logger)
    {
        logger.LogInformation("请求路径:{Path}", context.Request.Path);

        // 或者从HttpContext的RequestServices解析
        var anotherService = context.RequestServices.GetService<IAnotherService>();

        await _next(context);
    }
}

9.4 解析服务的注意事项

注意事项 说明
避免服务定位器反模式 尽量使用构造函数注入,而非App.GetService
Scoped服务作用域 在Singleton中不能直接注入Scoped服务
生命周期不匹配 Singleton服务不应依赖Transient/Scoped服务
延迟解析 需要时可以使用Lazy<T>延迟解析
// ❌ 错误:Singleton中注入Scoped服务
public class BadSingleton : ISingleton
{
    private readonly IScopedService _scoped; // 这会导致问题!

    public BadSingleton(IScopedService scoped)
    {
        _scoped = scoped;
    }
}

// ✅ 正确:通过IServiceProvider创建作用域
public class GoodSingleton : ISingleton
{
    private readonly IServiceProvider _serviceProvider;

    public GoodSingleton(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void DoWork()
    {
        using var scope = _serviceProvider.CreateScope();
        var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
        scopedService.Execute();
    }
}

10. 生命周期管理详解

10.1 Transient(瞬时)详解

每次从DI容器请求服务时都会创建一个新实例:

public class TransientDemo : ITransient
{
    public Guid InstanceId { get; } = Guid.NewGuid();

    public TransientDemo()
    {
        Console.WriteLine($"TransientDemo 创建:{InstanceId}");
    }
}

// 验证瞬时行为
public class TransientTestService : IDynamicApiController
{
    private readonly TransientDemo _instance1;
    private readonly TransientDemo _instance2;

    public TransientTestService(TransientDemo instance1, TransientDemo instance2)
    {
        _instance1 = instance1;
        _instance2 = instance2;
    }

    public object GetInstanceIds()
    {
        // 两个实例的ID不同
        return new
        {
            Instance1 = _instance1.InstanceId,
            Instance2 = _instance2.InstanceId,
            AreSame = _instance1.InstanceId == _instance2.InstanceId // false
        };
    }
}

适用场景:

  • 轻量级无状态服务
  • 不持有共享资源的服务
  • 需要确保每次使用都是全新实例

10.2 Scoped(作用域)详解

在同一个HTTP请求(作用域)内,返回同一个实例:

public class ScopedDemo : IScoped
{
    public Guid InstanceId { get; } = Guid.NewGuid();
    public int CallCount { get; set; }
}

// 验证作用域行为
public class ScopedTestService : IDynamicApiController
{
    private readonly ScopedDemo _instance1;
    private readonly ScopedDemo _instance2;

    public ScopedTestService(ScopedDemo instance1, ScopedDemo instance2)
    {
        _instance1 = instance1;
        _instance2 = instance2;
    }

    public object GetInstanceIds()
    {
        _instance1.CallCount++;
        _instance2.CallCount++; // 实际上操作的是同一个实例

        // 同一请求中两个实例ID相同
        return new
        {
            Instance1 = _instance1.InstanceId,
            Instance2 = _instance2.InstanceId,
            AreSame = _instance1.InstanceId == _instance2.InstanceId, // true
            CallCount = _instance1.CallCount // 2
        };
    }
}

适用场景:

  • 数据库上下文(DbContext)
  • 工作单元(Unit of Work)
  • 需要在同一请求中共享状态的服务
  • 持有需要在请求结束时释放的资源

10.3 Singleton(单例)详解

整个应用程序生命周期中只创建一个实例:

public class SingletonDemo : ISingleton
{
    public Guid InstanceId { get; } = Guid.NewGuid();
    public DateTime CreatedAt { get; } = DateTime.Now;
    private int _requestCount;

    public int IncrementAndGetCount()
    {
        return Interlocked.Increment(ref _requestCount);
    }
}

// 验证单例行为
public class SingletonTestService : IDynamicApiController
{
    private readonly SingletonDemo _instance;

    public SingletonTestService(SingletonDemo instance)
    {
        _instance = instance;
    }

    public object GetInstanceInfo()
    {
        return new
        {
            InstanceId = _instance.InstanceId,    // 每次请求都相同
            CreatedAt = _instance.CreatedAt,       // 创建时间不变
            RequestCount = _instance.IncrementAndGetCount() // 递增
        };
    }
}

适用场景:

  • 配置服务
  • 缓存服务
  • 连接池
  • 线程安全的共享状态

注意: 单例服务必须是 线程安全 的!

10.4 生命周期对比实验

// 创建三种生命周期的服务
public class TransientService : ITransient
{
    public Guid Id { get; } = Guid.NewGuid();
}

public class ScopedService : IScoped
{
    public Guid Id { get; } = Guid.NewGuid();
}

public class SingletonService : ISingleton
{
    public Guid Id { get; } = Guid.NewGuid();
}

// 对比测试
public class LifecycleTestService : IDynamicApiController
{
    private readonly TransientService _transient1;
    private readonly TransientService _transient2;
    private readonly ScopedService _scoped1;
    private readonly ScopedService _scoped2;
    private readonly SingletonService _singleton1;
    private readonly SingletonService _singleton2;

    public LifecycleTestService(
        TransientService transient1, TransientService transient2,
        ScopedService scoped1, ScopedService scoped2,
        SingletonService singleton1, SingletonService singleton2)
    {
        _transient1 = transient1;
        _transient2 = transient2;
        _scoped1 = scoped1;
        _scoped2 = scoped2;
        _singleton1 = singleton1;
        _singleton2 = singleton2;
    }

    /// <summary>
    /// 生命周期对比(多次调用此接口观察结果)
    /// </summary>
    public object GetLifecycleComparison()
    {
        return new
        {
            Transient = new
            {
                Instance1 = _transient1.Id,
                Instance2 = _transient2.Id,
                Same = _transient1.Id == _transient2.Id  // false - 每次都不同
            },
            Scoped = new
            {
                Instance1 = _scoped1.Id,
                Instance2 = _scoped2.Id,
                Same = _scoped1.Id == _scoped2.Id        // true - 同一请求相同
            },
            Singleton = new
            {
                Instance1 = _singleton1.Id,
                Instance2 = _singleton2.Id,
                Same = _singleton1.Id == _singleton2.Id  // true - 始终相同
            }
        };
    }
}

10.5 IDisposable与生命周期

实现IDisposable的服务会在其生命周期结束时自动被释放:

public class DisposableService : IScoped, IDisposable
{
    private bool _disposed;

    public void DoWork()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(DisposableService));
        Console.WriteLine("执行工作");
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _disposed = true;
            Console.WriteLine("DisposableService 被释放");
            // 释放非托管资源
        }
    }
}

// Transient: 在作用域结束时释放
// Scoped:    在HTTP请求结束时释放
// Singleton: 在应用程序关闭时释放

11. 依赖注入最佳实践

11.1 设计原则

原则1:优先使用构造函数注入

// ✅ 推荐:构造函数注入
public class GoodService
{
    private readonly IUserRepository _repo;

    public GoodService(IUserRepository repo)
    {
        _repo = repo ?? throw new ArgumentNullException(nameof(repo));
    }
}

// ❌ 避免:服务定位器模式
public class BadService
{
    public void DoWork()
    {
        var repo = App.GetService<IUserRepository>(); // 不推荐
    }
}

原则2:面向接口编程

// ✅ 推荐:依赖接口
public class OrderService
{
    private readonly IPaymentService _payment;

    public OrderService(IPaymentService payment) // 依赖接口
    {
        _payment = payment;
    }
}

// ❌ 避免:依赖具体实现
public class BadOrderService
{
    private readonly AlipayService _payment;

    public BadOrderService(AlipayService payment) // 依赖具体类
    {
        _payment = payment;
    }
}

原则3:选择正确的生命周期

服务类型 推荐生命周期 原因
无状态工具类 Transient 无共享状态需求
DbContext Scoped 需要请求级别的事务管理
配置服务 Singleton 配置信息全局共享
HTTP客户端 Singleton 避免Socket耗尽
缓存服务 Singleton 全局共享缓存
日志服务 Singleton 全局共享
用户上下文 Scoped 与当前请求绑定

11.2 常见错误

错误1:Captive Dependency(俘获依赖)

// ❌ 严重错误:Singleton持有Scoped服务
public class CaptiveSingleton : ISingleton
{
    private readonly MyDbContext _dbContext; // Scoped服务被Singleton捕获!

    public CaptiveSingleton(MyDbContext dbContext)
    {
        _dbContext = dbContext;
        // _dbContext 会在第一个请求结束后被释放
        // 后续请求使用的是已释放的DbContext!
    }
}

// ✅ 正确:使用IServiceScopeFactory
public class CorrectSingleton : ISingleton
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CorrectSingleton(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task DoWorkAsync()
    {
        using var scope = _scopeFactory.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
        await dbContext.Users.ToListAsync();
    }
}

错误2:构造函数过多参数

// ❌ 构造函数参数过多(代码异味)
public class GodService
{
    public GodService(
        IUserRepo userRepo, IOrderRepo orderRepo,
        IProductRepo productRepo, ICategoryRepo categoryRepo,
        ILogger<GodService> logger, IMapper mapper,
        ICacheService cache, IEmailService email,
        INotificationService notification, IAuditService audit)
    { }
}

// ✅ 重构:拆分为更小的服务
public class UserOrderService
{
    public UserOrderService(IUserRepo userRepo, IOrderRepo orderRepo, ILogger logger)
    { }
}

public class ProductService
{
    public ProductService(IProductRepo productRepo, ICategoryRepo categoryRepo, ILogger logger)
    { }
}

11.3 单元测试中的依赖注入

using Moq;
using Xunit;

public class UserServiceTests
{
    [Fact]
    public async Task GetById_ShouldReturnUser()
    {
        // Arrange - 创建Mock对象
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(r => r.GetByIdAsync(1))
            .ReturnsAsync(new User { Id = 1, Name = "张三" });

        var mockLogger = new Mock<ILogger<UserService>>();

        // 创建被测试的服务实例
        var service = new UserService(mockRepo.Object, mockLogger.Object);

        // Act
        var result = await service.GetByIdAsync(1);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(1, result.Id);
        Assert.Equal("张三", result.Name);

        // 验证方法被调用
        mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once);
    }
}

11.4 依赖注入检查清单

在进行依赖注入设计时,请检查以下要点:

  • 所有依赖都通过构造函数注入
  • 依赖的是接口而非具体实现
  • 生命周期选择正确(Transient/Scoped/Singleton)
  • 没有Captive Dependency问题
  • Singleton服务是线程安全的
  • 构造函数参数不超过5-7个
  • 没有使用服务定位器反模式(除非必要)
  • IDisposable服务正确释放资源
  • 可以方便地进行单元测试

总结

本章全面介绍了Furion框架中的依赖注入与服务注册机制。Furion通过ITransientIScopedISingleton三个标记接口,实现了优雅的自动服务注册,大大简化了传统的手动注册过程。

关键要点:

  • 接口标记方式ITransient/IScoped/ISingleton)是Furion推荐的服务注册方式
  • 构造函数注入是首选的依赖注入方式,清晰且易于测试
  • App.GetService()提供了在任何地方解析服务的能力,但应谨慎使用
  • 生命周期选择需要根据服务的特性仔细考虑,避免Captive Dependency问题
  • Autofac集成提供了属性注入、AOP等高级功能
  • 遵循面向接口编程SOLID原则是依赖注入的最佳实践

通过本教程前五章的学习,你已经掌握了Furion框架的核心概念和基础开发技能,包括框架概述、环境搭建、项目架构、动态WebAPI开发以及依赖注入系统。这些知识足以让你开始使用Furion构建实际的Web应用项目。


上一章: 第04章-动态WebAPI开发 | 返回目录 | 下一章: 第06章-数据库操作与EF-Core集成