概述
silky 框架的底层 IoC 容器是 Autofac,通过将 AutofacServiceProviderFactory 注入到 .NET 主机中,替换默认的 IServiceCollection 容器,从而获得更强大的依赖注入能力。
除了直接使用 IServiceCollection.AddXxx() 或在 Autofac ContainerBuilder 中显式注册服务外,silky 还提供了一套约定式依赖注入机制:只要实现特定的标记接口,即可在框架启动时自动完成扫描和注册,无需任何手动注册代码。
约定注入接口
silky 提供三个标记接口,对应 Autofac 的三种生命周期:
| 接口 | 对应 Autofac 生命周期 | 说明 |
|---|---|---|
ITransientDependency | InstancePerDependency | 瞬时:每次解析都创建新实例 |
IScopedDependency | InstancePerLifetimeScope | 作用域:同一 Scope 内复用同一实例 |
ISingletonDependency | SingleInstance | 单例:整个应用生命周期唯一实例 |
使用方式
// 瞬时:每次注入都是新实例
public class OrderService : IOrderService, ITransientDependency
{
}
// 作用域:在同一请求(Scope)内复用同一实例
public class CurrentUserContext : ICurrentUserContext, IScopedDependency
{
}
// 单例:整个应用共享同一实例
public class LocalServiceEntryManager : IServiceEntryManager, ISingletonDependency
{
}
框架在 DefaultDependencyRegistrar.Register() 中扫描所有已加载程序集,自动发现并注册这些类:
public class DefaultDependencyRegistrar : IDependencyRegistrar
{
public void Register(ContainerBuilder builder, ITypeFinder typeFinder)
{
var refAssemblies = typeFinder.GetAssemblies();
foreach (var assembly in refAssemblies)
{
// 单例注册
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.SingleInstance();
// 瞬时注册
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.InstancePerDependency();
// 作用域注册
builder.RegisterAssemblyTypes(assembly)
.Where(t => typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(t)
&& !t.GetCustomAttributes().OfType<InjectNamedAttribute>().Any())
.PropertiesAutowired()
.AsImplementedInterfaces()
.AsSelf()
.InstancePerLifetimeScope();
}
}
}
注意两个关键点:
- 所有约定注入的类都启用了
PropertiesAutowired(),即属性注入也受 Autofac 管理 - 带有
[InjectNamed]特性的类会被跳过,由NamedServiceDependencyRegistrar单独处理
属性注入(PropertiesAutowired)
通过约定接口注册的类,其公共属性会被 Autofac 自动注入(属性注入),无需在构造函数中声明依赖:
public class DefaultServerMessageReceivedHandler : ISingletonDependency
{
// 属性注入:无需构造函数参数
public ILogger<DefaultServerMessageReceivedHandler> Logger { get; set; }
// 构造函数注入依然可用,两者可以混用
public DefaultServerMessageReceivedHandler(IServiceEntryLocator locator)
{
_locator = locator;
}
}
提示
silky 框架内部大量使用属性注入来注入 ILogger<T>,这样可以避免每个类都在构造函数中声明日志依赖。
命名注入(Named Service)
当同一接口有多个实现类,且需要通过名称区分时,使用 [InjectNamed] 特性:
[InjectNamed("Rpc")]
public class RpcParameterResolver : IParameterResolver, ITransientDependency
{
}
[InjectNamed("Dict")]
public class DictParameterResolver : IParameterResolver, ITransientDependency
{
}
[InjectNamed("Http")]
public class HttpParameterResolver : IParameterResolver, ITransientDependency
{
}
注册后,通过 Autofac 的命名解析获取特定实现:
// 根据消息的 ParameterType 名称动态解析对应的参数解析器
var parameterResolver = EngineContext.Current
.ResolveNamed<IParameterResolver>(message.ParameterType.ToString());
NamedServiceDependencyRegistrar 负责扫描所有带 [InjectNamed] 特性的类,并按名称注册到 Autofac 容器:
// 伪代码:按名称注册
builder.RegisterType<RpcParameterResolver>()
.Named<IParameterResolver>("Rpc")
.InstancePerDependency();
模块中显式注册(RegisterServices)
除约定注入外,每个 SilkyModule 可以在 RegisterServices(ContainerBuilder builder) 方法中进行显式注册,适合需要精细控制生命周期或注册方式的场景:
public class RpcModule : SilkyModule
{
protected override void RegisterServices(ContainerBuilder builder)
{
// 精细控制:按实现类注册,指定名称,或使用工厂方法
builder.RegisterType<DefaultExecutor>()
.As<IExecutor>()
.SingleInstance();
}
}
通过 IConfigureService 注册
实现 IConfigureService 接口的类,在 SilkyEngine.ConfigureServices() 阶段会被通过反射自动发现并执行,可以使用 IServiceCollection 进行服务注册:
public class RpcConfigureService : IConfigureService
{
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddHttpContextAccessor();
services.AddOptions<RpcOptions>()
.Bind(configuration.GetSection(RpcOptions.Rpc));
}
}
优先级与注册顺序
silky 的依赖注入分为以下几个阶段,按此顺序执行:
(1) IConfigureService 实现类(通过反射发现,使用 IServiceCollection 注册)
↓
(2) SilkyModule.ConfigureServices()(各模块,使用 IServiceCollection 注册)
↓
(3) SilkyModule.RegisterServices()(各模块,使用 ContainerBuilder 注册)
↓
(4) DefaultDependencyRegistrar(约定扫描,使用 ContainerBuilder 自动注册)
↓
(5) NamedServiceDependencyRegistrar(命名服务扫描,使用 ContainerBuilder 注册)
Warning
Autofac 中后注册的覆盖先注册的(默认行为)。因此,约定扫描(步骤 4/5)的注册在模块显式注册(步骤 3)之后执行,这意味着如果模块和约定类都注册了同一接口,最终生效的是约定扫描的实现。若要保证模块注册优先,需使用 PreserveExistingDefaults()。
框架上下文(EngineContext)
在无法通过构造函数或属性注入获取服务的场景(如静态类、工厂方法内部),可以通过 EngineContext.Current 手动解析:
// 解析已注册的服务
var executor = EngineContext.Current.Resolve<IExecutor>();
// 按名称解析
var resolver = EngineContext.Current.ResolveNamed<IParameterResolver>("Rpc");
// 判断某服务是否已注册
var isRegistered = EngineContext.Current.IsRegistered<IFallbackProvider>();
EngineContext.Current 是 IEngine(SilkyEngine)的单例引用,其 ServiceProvider 属性在 InitSilkyHostedService 启动后被初始化,此后可在整个应用生命周期中安全使用。
选择合适的注入方式
| 场景 | 推荐方式 |
|---|---|
| 框架内核服务(Silky 自身组件) | ISingletonDependency 约定注入 |
| 每次请求独立的业务服务 | IScopedDependency 约定注入 |
| 无状态工具类、帮助器 | ITransientDependency 约定注入 |
| 同接口多实现,按名称区分 | [InjectNamed] + 约定注入 |
| 需要精细控制注册参数 | 模块 RegisterServices() 显式注册 |
| 第三方库、ASP.NET Core 中间件 | IConfigureService 或模块 ConfigureServices() |
