第五章:依赖注入与服务注册
目录
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会自动扫描程序集中实现了ITransient、IScoped或ISingleton接口的类,并按照以下规则注册:
| 场景 | 注册方式 | 示例 |
|---|---|---|
| 类实现了业务接口+标记接口 | 以业务接口为服务类型注册 | 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通过ITransient、IScoped、ISingleton三个标记接口,实现了优雅的自动服务注册,大大简化了传统的手动注册过程。
关键要点:
- 接口标记方式(
ITransient/IScoped/ISingleton)是Furion推荐的服务注册方式 - 构造函数注入是首选的依赖注入方式,清晰且易于测试
- **App.GetService
()**提供了在任何地方解析服务的能力,但应谨慎使用 - 生命周期选择需要根据服务的特性仔细考虑,避免Captive Dependency问题
- Autofac集成提供了属性注入、AOP等高级功能
- 遵循面向接口编程和SOLID原则是依赖注入的最佳实践
通过本教程前五章的学习,你已经掌握了Furion框架的核心概念和基础开发技能,包括框架概述、环境搭建、项目架构、动态WebAPI开发以及依赖注入系统。这些知识足以让你开始使用Furion构建实际的Web应用项目。