.net core 源码分析(9) 依赖注入(DI)-Dependency Injection
.NET Core 内置了一个轻量级的 DI 容器,用于管理依赖项的注册和解析。常见的流程是:1.将服务及其实现类型注册到 IServiceCollection
。2.构建服务提供者 IServiceProvider,
使用 IServiceCollection
存储依赖注册信息。在InitializeServiceProvider()中通过 IServiceProviderFactory
构建 IServiceProvider
。 IServiceProviderFactory
是一个工厂类,默认情况下 .NET Core 使用 DefaultServiceProviderFactory
来创建 IServiceProvider
,用户也可以设置自定义的工厂方法来实现集成第三方DI插件。3.通过 IServiceProvider
获取服务实例。
public class Program { // Entry point for the application. public static Task Main(string[] args) { var host = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseKestrel() // Each of these three sets ApplicationName to the current assembly, which is needed in order to // scan the assembly for HostingStartupAttributes. // .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups") // .Configure(_ => { }) .UseStartup<Startup>(); }) .Build(); return host.RunAsync(); } }
在HostBuilder的构造函数中就给_serviceProviderFactory设置了默认值。用于在InitializeServiceProvider()中通过该工厂方法创建IServiceProvider。
public partial class HostBuilder : IHostBuilder { //工厂方法 private IServiceFactoryAdapter _serviceProviderFactory; //省略了一些代码。。 /// <summary> /// Initializes a new instance of <see cref="HostBuilder"/>. /// </summary> public HostBuilder() { _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory()); } }
.NET Core 使用 IServiceCollection
来收集注册信息,通过 IServiceProviderFactory
的 CreateServiceProvider(containerBuilder)
方法来构建IServiceProvider
。现在来看下具体实现。
private void InitializeServiceProvider() { //创建IServiceCollection var services = new ServiceCollection(); PopulateServiceCollection( services, _hostBuilderContext!, _hostingEnvironment!, _defaultProvider!, _appConfiguration!, () => _appServices!); //通过被暂存的action列表,收集注册信息。 foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions) { configureServicesAction(_hostBuilderContext!, services); } //收集注册信息完成,通过工厂的CreateBuilder方法创建DI容器。 //IServiceProviderFactory不但是IServiceProvider的工厂类,还是containerBuilder的工厂类 object containerBuilder = _serviceProviderFactory.CreateBuilder(services); //Startup.ConfigureContainer在这里被执行。 foreach (IConfigureContainerAdapter containerAction in _configureContainerActions) { containerAction.ConfigureContainer(_hostBuilderContext!, containerBuilder); } //最终目的就是通过收集到的注册信息来构建IServiceProvider _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder); }
到这里你可能已经发现了一个问题,为什么由 IServiceProviderFactory.CreateBuilder(IServiceCollection)
创建的 containerBuilder
是object
类型?下面介绍IServiceProviderFactory
。
public interface IServiceProviderFactory<TContainerBuilder> where TContainerBuilder : notnull { /// <summary> /// Creates a container builder from an <see cref="IServiceCollection"/>. /// </summary> /// <param name="services">The collection of services</param> /// <returns>A container builder that can be used to create an <see cref="IServiceProvider"/>.</returns> TContainerBuilder CreateBuilder(IServiceCollection services); /// <summary> /// Creates an <see cref="IServiceProvider"/> from the container builder. /// </summary> /// <param name="containerBuilder">The container builder</param> /// <returns>An <see cref="IServiceProvider"/></returns> IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder); }
IServiceProviderFactory<TContainerBuilder> where TContainerBuilder : notnull
是一个泛型接口。用于创建服务提供者 (IServiceProvider) 以及与依赖注入容器的构建器 (TContainerBuilder) 进行交互。这个接口的主要用途是在不同的依赖注入容器实现之间提供一个抽象层,使得开发者可以灵活地更换容器实现而不需要修改业务逻辑。Autofac是.NET领域最为流行的IoC框架之一。下面演示下如何使用Autofac替换默认容器。
1.实现AutofacServiceProviderFactory类,TContainerBuilder指定为Autofac.ContainerBuilder。
public class AutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder> { private readonly ContainerBuilder _builder; private IServiceCollection _services = default!; public AutofacServiceProviderFactory(ContainerBuilder builder) { _builder = builder; } /// <summary> ///创建一个容器构建器。 /// </summary> /// <param name="services">服务集合</param> /// <returns>ioc容器构建器。</returns> public ContainerBuilder CreateBuilder(IServiceCollection services) { _services = services; //把.Net Core 已经收集到的注册信息给同步到Autofac.ContainerBuilder中 _builder.Populate(services); return _builder; } public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder) { //返回自定义的IServiceProvider来作为系统的服务提供者 return new AutofacServiceProvider(containerBuilder.Build()); } }
2.替换HostBuilder中默认的IServiceProviderFactory
。
public class Program { // Entry point for the application. public static Task Main(string[] args) { //Autofac的容器构造器 var containerBuilder = new Autofac.ContainerBuilder(); var host = new HostBuilder() //替换默认的IServiceProviderFactory .UseServiceProviderFactory(new AbpAutofacServiceProviderFactory(containerBuilder)) .ConfigureWebHost(webHostBuilder => { webHostBuilder .UseKestrel() // Each of these three sets ApplicationName to the current assembly, which is needed in order to // scan the assembly for HostingStartupAttributes. // .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups") // .Configure(_ => { }) .UseStartup<Startup>(); }) .Build(); return host.RunAsync(); } }
3.这个时候我们就可以在Startup文件中配置ContainerBuilder了。
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(); } //这里传入的参数就是在上面new Autofac.ContainBuilder()对象,这里的参数类型,要和IServiceProviderFactory<TContainerBuilder>的实现类中的TcontainerBuilder保持一致。 public void ConfigureContainer(Autofac.ContainerBuilder builder) { //这里就对ContainerBuilder进行配置。 } //Configure 是用于配置 HTTP 请求管道的地方 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { //省略了一下代码 app.UseAuthorization(); } }
回到DefaultServiceProviderFactory
.NET Core 默认的IServiceProviderFactory,TContainerBuilder=IServiceCollection。这里创建的ContainerBuilder 就是ServiceCollection自身,最终通过IServiceCollection的扩展方法BuildServiceProvider()创建IServiceProvider。
/// <summary> /// ContainerBuilder指定为IServiceCollection。 /// </summary> public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection> { private readonly ServiceProviderOptions _options; public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default) { } public DefaultServiceProviderFactory(ServiceProviderOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } ///创建ContainerBuilder,返回ServiceCollection自身 public IServiceCollection CreateBuilder(IServiceCollection services) { return services; } /// 通过ContainerBuilder 创建IServiceProvider public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) { //调用IServiceCollection的BuildServiceProvider 扩展方法 return containerBuilder.BuildServiceProvider(_options); } }
ServiceCollection
是 .NET Core 中依赖注入(Dependency Injection, DI)系统的核心类之一。它实现了 IServiceCollection
接口,用于注册应用程序所需的服务。服务可以是各种类型的依赖,Transient、Scoped和Singleton服务。
public class ServiceCollection : IServiceCollection { private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>(); }
public class ServiceDescriptor { public ServiceLifetime Lifetime { get; } /// <summary> /// Get the key of the service, if applicable. /// </summary> public object? ServiceKey { get; } /// <summary> /// Gets the <see cref="Type"/> of the service. /// </summary> public Type ServiceType { get; } private Type? _implementationType; private object? _implementationInstance; private object? _implementationFactory; //省略了一下代码。
}
现在我们来演示一下注册接口 IServiceA
和 IServiceB
。
serviceCollection.AddTransient<IServiceA, ServiceA0>(); serviceCollection.AddTransient<IServiceA, ServiceA1>(); serviceCollection.AddScoped<IServiceB, ServiceB1>();
上面的代码执行以后,ServiceCollection中就会有三条ServiceDescriptor数据,第一条和第二条他们的ServiceType都是IServiceA接口,只是ImplementationType不同。ServiceDescriptor
是描述服务的元数据,用于在依赖注入容器中注册服务时,定义服务的类型、实现、生命周期等信息。 ResultCache
是一个结构,用于缓存服务解析的结果。它的主要目的是提高服务解析的性能,尤其是在需要多次解析同一服务的场景下。ResultCache
包含以下关键元素:Location: 表示缓存的生命周期位置(例如 None
、Root
、Scope
、Dispose
),Key: 一个 ServiceCacheKey
,用于唯一标识缓存项。ServiceCacheKey
本身包含 ServiceIdentifier
(标识服务类型和键)和槽位信息。Slot值来源举例说明: IServiceA接口注册顺序为ServiceA0,ServiceA1,ServiceA2,对应的Slot是他们的注册顺序倒序排列的索引,ServiceA0.Slot=2,ServiceA1.Slot=1,,ServiceA2.Slot=0,如下图:
在服务解析过程中,容器可能会生成一个 ResultCache
实例,用于存储解析结果的位置和缓存的键。ResultCache
的 Key
(ServiceCacheKey
)由 ServiceIdentifier
生成,而 ServiceIdentifier
是通过 ServiceDescriptor
中的 ServiceType
和 ServiceKey
创建的。这样确保了每个 ServiceDescriptor
都可以对应一个唯一的 ResultCache
键。ResultCache
的 Location
(CallSiteResultCacheLocation
)根据服务的生命周期类型(如 Singleton
、Scoped
)来决定,这与 ServiceDescriptor
中的 Lifetime
密切相关。
对于ResultCache和ServiceDescriptot比较。ResultCache主要包含了Slot。在针对同一个接口进行多次注册实现时候,如果获取这个接口的实现,ServiceProvider就可以根据Slot最小的来返回最后一个注册的实现。
未完待续
.net core 源码分析