《服务注册》、《服务消费》和《生命周期》主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现。在过去的多个版本更迭过程中,依赖注入框架的底层实现一直都在发生改变,加上底层的涉及的大都是内容接口和类型,所以我们不打算涉及太过细节的层面。
一、ServiceProviderEngine & ServiceProviderEngineScope
对于依赖注入的底层设计和实现来说,ServiceProviderEngine和ServiceProviderEngineScope是两个最为核心的类型。顾名思义,ServiceProviderEngine表示提供服务实例的提供引擎,服务实例最终是通过该引擎提供的,在一个应用范围内只存在一个全局唯一的ServiceProviderEngine对象。ServiceProviderEngineScope代表服务范围,它利用对提供服务实例的缓存实现对生命周期的控制。ServiceProviderEngine实现了接口IServiceProviderEngine,从如下的代码片段可以看出,一个ServiceProviderEngine对象同时也是一个IServiceProvider对象,还是一个IServiceScopeFactory对象。
internal interface IServiceProviderEngine : IServiceProvider, IDisposable, IAsyncDisposable
{
void ValidateService(ServiceDescriptor descriptor);
IServiceScope RootScope { get; }
} internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
{
public IServiceScope RootScope { get; }
public IServiceScope CreateScope();
...
}
ServiceProviderEngine的RootScope属性返回的IServiceScope对象是为根容器提供的服务范围。作为一个IServiceScopeFactory对象,ServiceProviderEngine的CreateScope会创建一个新的服务范围,这两种服务范围都通过一个ServiceProviderEngineScope对象来表示。
internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable
{
public ServiceProviderEngine Engine { get; }
public IServiceProvider ServiceProvider { get; }
public object GetService(Type serviceType);
}
如上面的代码片段所示,一个ServiceProviderEngineScope对象不仅是一个IServiceScope对象,还是一个IServiceProvider对象。在《生命周期》中,我们说表示服务范围的IServiceScope对象是对一个表示依赖注入容器的IServiceProvider对象的封装,实际上两者合并为同一个ServiceProviderEngineScope对象,一个ServiceProviderEngineScope对象的ServiceProvider属性返回的就是它自己。换句话说,我们所谓的子容器和它所在的服务范围引用的都是同一个ServiceProviderEngineScope对象。
下图进一步揭示了ServiceProviderEngine和ServiceProviderEngineScope之间的关系。对于一个通过调用ServiceProviderEngine对象的CreateScope创建的ServiceProviderEngineScope来说,由于它同时也是一个IServiceProvider对象,如果我们调用它的GetService<IServiceProvider>方法,该方法同样返回它自己。如果我们调用它的GetService<IServiceScopeFactory>方法,它返回创建它的ServiceProviderEngine对象,也就是该方法和Engine属性返回同一个对象。
依赖注入框架提供的服务实例最终是通过ServiceProviderEngine对象提供的。从上面给出的代码片段可以看出,ServiceProviderEngine是一个抽象类,.NET Core依赖注入框架提供了如下四个具体的实现类型,默认使用的是DynamicServiceProviderEngine。
- RuntimeServiceProviderEngine:采用反射的方式提供服务实例;
- ILEmitServiceProviderEngine:采用IL Emit的方式提供服务实例;
- ExpressionsServiceProviderEngine:采用表达式树的方式提供服务实例;
- DynamicServiceProviderEngine:根据请求并发数量动态决定最终的服务实例提供方案(反射和者IL Emit或者反射与表达式树,是否选择IL Emit取决于当前运行时是否支持Reflection Emit)。
4.4.2. ServiceProvider
调用IServiceCollection集合的扩展方法BuildServiceProvider创建的是一个ServiceProvider对象。作为根容器的ServiceProvider对象,和前面介绍的ServiceProviderEngine和ServiceProviderEngineScope对象,一起构建了整个依赖注入框架的设计蓝图。
在利用IServiceCollection集合创建ServiceProvider对象的时候,提供的服务注册将用来创建一个具体的ServiceProviderEngine对象。该ServiceProviderEngine对象的RootScope就是它创建的一个ServiceProviderEngineScope对象,子容器提供的Singleton服务实例由它维护。如果调用ServiceProvider对象的GetService<IServiceProvider>方法,返回的其实不是它自己,而是作为RootScope的ServiceProviderEngineScope对象(调用ServiceProviderEngineScope对象的GetService<IServiceProvider>方法返回的是它自己)。
ServiceProvider和ServiceProviderEngineScope都实现了IServiceProvider接口,如果我们调用了它们的GetService<IServiceScopeFactory>方法,返回的都是同一个ServiceProviderEngine对象。这一个特性决定了调用它们的CreateScope扩展方法都会创建一个新的ServiceProviderEngineScope对象作为子容器。综上所述,我们针对依赖注入框架总结出如下的特性:
- ServiceProviderEngine的唯一性:整个服务提供体系只存在一个唯一的ServiceProviderEngine对象。
- ServiceProviderEngine与IServiceFactory的同一性:唯一存在的ServiceProviderEngine会作为创建服务范围的IServiceFactory工厂。
- ServiceProviderEngineScope和IServiceProvider的同一性:表示服务范围的ServiceProviderEngineScope同时也是作为服务提供者的依赖注入容器。
为了印证我们总结出来的特性,我们编写的测试代码。由于设计的ServiceProviderEngine和ServiceProviderEngineScope都是内部类型,我们只能采用反射的方式得到它们的属性或者字段成员。上面总结的这些特征体现在如下几组调试断言中。
class Program
{
static void Main()
{
var (engineType, engineScopeType) = ResolveTypes();
var root = new ServiceCollection().BuildServiceProvider();
var child1 = root.CreateScope().ServiceProvider;
var child2 = root.CreateScope().ServiceProvider; var engine = GetEngine(root);
var rootScope = GetRootScope(engine, engineType); //ServiceProviderEngine的唯一性
Debug.Assert(ReferenceEquals(GetEngine(rootScope, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child1, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child2, engineScopeType), engine)); //ServiceProviderEngine和IServiceScopeFactory的同一性
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceScopeFactory>(), engine)); //ServiceProviderEngineScope提供的IServiceProvider是它自己
//ServiceProvider提供的IServiceProvider是RootScope
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceProvider>(), rootScope));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceProvider>(), child1));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceProvider>(), child2)); //ServiceProviderEngineScope和IServiceProvider的同一性
Debug.Assert(ReferenceEquals((rootScope).ServiceProvider, rootScope));
Debug.Assert(ReferenceEquals(((IServiceScope)child1).ServiceProvider, child1));
Debug.Assert(ReferenceEquals(((IServiceScope)child2).ServiceProvider, child2));
} static (Type Engine, Type EngineScope) ResolveTypes()
{
var assembly = typeof(ServiceProvider).Assembly;
var engine = assembly.GetTypes().Single(it => it.Name == "IServiceProviderEngine");
var engineScope = assembly.GetTypes().Single(it => it.Name == "ServiceProviderEngineScope");
return (engine, engineScope);
} static object GetEngine(ServiceProvider serviceProvider)
{
var field = typeof(ServiceProvider).GetField("_engine", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(serviceProvider);
} static object GetEngine(object enginScope, Type engineScopeType)
{
var property = engineScopeType.GetProperty("Engine", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(enginScope);
} static IServiceScope GetRootScope(object engine, Type engineType)
{
var property = engineType.GetProperty("RootScope", BindingFlags.Instance | BindingFlags.Public);
return (IServiceScope)property.GetValue(engine);
}
}
三、注入IServiceProvider对象
在《依赖注入模式》中,我们从“Service Locator”设计模式是反模式的角度说明了为什么不推荐在服务中注入IServiceProvider对象。不过反模式并不就等于是完全不能用的模式,有些情况下直接在服务构造函数中注入作为依赖注入容器的IServiceProvider对象可能是最快捷省事的解决方案。对于IServiceProvider对象的注入,有个细节大家可能忽略或者误解。
读者朋友们可以试着思考这么一个问题:如果我们在某个服务中注入了IServiceProvider对象,当我们利用某个IServiceProvider对象来提供该服务实例的时候,注入的IServiceProvider对象是它自己吗?以如下所示的代码片段为例,我们定义了两个在构造函数中注入了IServiceProvider对象的服务类型SingletonService和ScopedService,并按照命名所示的生命周期进行了注册。
class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
} public class SingletonService
{
public IServiceProvider ApplicationServices { get; }
public SingletonService(IServiceProvider serviceProvider) => ApplicationServices = serviceProvider;
} public class ScopedService
{
public IServiceProvider RequestServices { get; }
public ScopedService(IServiceProvider serviceProvider) => RequestServices = serviceProvider;
}
}
我们最终利用一个作为子容器的IServiceProvider对象(ServiceProviderEngineScope对象)来提供这来个服务类型的实例,并通过调试断言确定注入的IServiceProvider对象是否就是作为当前依赖注入容器的ServiceProviderEngineScope对象。如果在Debug模式下运行上述的测试代码,我们会发现第一个断言是成立的,第二个则不成立。
再次回到两个服务类型的定义,SingletonService和ScopedService中通过注入IServiceProvider对象初始化的属性分别被命名为ApplicationServices和RequestServices,意味着它们希望注入的分别是针对当前应用程序的根容器和针对请求的子容器。当我们利用针对请求的子容器来提供针对这两个类型的服务实例时,如果注入的当前子容器的话,就与ApplicationServices的意图不符。所以在提供服务实例的注入的IServiceProvider对象取决于采用的生命周期模式,具体策略为:
- Singleton:注入的是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
- Scoped和Transient:如果当前IServiceProvider对象类型为ServiceProviderEngineScope,注入的就是它自己,如果是一个ServiceProvider对象,注入的还是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
基于生命周期模式注入IServiceProvider对象的策略可以通过如下这个测试程序来验证。最后还有一点需要补充一下:我们将调用IServiceCollection集合的BuildServiceProvider扩展方法创建的ServiceProvider对象作为根容器,它对应的ServiceProviderEngine对象的RootScope属性返回作为根服务范围的ServiceProviderEngineScope对象,ServiceProvider、ServiceProviderEngine和ServiceProviderEngineScope这三个类型全部实现了IServiceProvider接口,这三个对象都可以视为根容器。
class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
var rootScope = serviceProvider.GetService<IServiceProvider>();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, child.GetRequiredService<IServiceProvider>()));
Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
}
}
[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配