znlgis 博客

GIS开发与技术分享

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

目录

  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应用项目。