依赖注入已经不是什么新鲜话题了,在.NET Framework
时期就已经出现了各种依赖注入框架,比如:autofac
、unity
等。只是在.net core
微软将它搬上了台面,不用再依赖第三方组件(那是不可能的)。依赖注入的概念与为什么选择使用依赖注入这里就不说了,网上搜一下就会有各种答案,今天这里的内容是看看在.net core
中,简单实用的依赖注入,背后到底做了哪些操作。
创建项目
今天演示不采用asp.net core
项目,而是采用.net core
控制台。相对前者,后者的操作逻辑更加完整简洁。
准备接口与对象,老User
了
1 | csharp复制代码public class UserService : IUserService |
1 | csharp复制代码/// <summary> |
1、实例化服务容器
1 | ini复制代码IServiceCollection services = new ServiceCollection(); |
这里出现了一个新对象:IServiceCollectionService
,也就是startup
中的
1 | arduino复制代码public void ConfigureServices(IServiceCollection services){} |
F12 可以看到,IServiceCollectionService
继承了IList<ServiceDescriptor>
接口,又引申出 ServiceDescriptor
对象。
1 | kotlin复制代码// |
而 ServiceDescriptor
对象见名知意就知道是用来描述服务信息的对象了。ServiceDescriptor
对象的内容有点多,在这里我们暂时只需要了解三个:
1 | csharp复制代码 /// <summary> |
- Lifetime:生命周期
- SericeType:服务对象
- ImplementationType:服务实现对象
第一步内容比较简单,就是声明一个服务容器集合。我们可以把 IServiceCollection
比做成银行,把服务比喻成 RMB ,现在银行有了,我们下一步肯定就是存 RMB 进去了。
2、添加服务
上面我们提到了 ServiceDescriptor
对象的三个属性:Lifetime
、ServiceType
、ImplementationType
。
再回过头看 services.AddTransient<IUserService, UserService>();
这段代码
- AddTransient 指定了
Lifetime
,也就是Transient
- IUserService 表示
ServiceType
- UserService 表示
ImplementationType
下面是 AddTransient
相关的源码,很是直观明了。就是将 RMB
存储银行,到底是 活期
、定期
还是理财
就由开发者自己去定义了。
1 | arduino复制代码public static IServiceCollection AddTransient( |
1 | csharp复制代码private static IServiceCollection Add( |
3、构建服务提供对象
上面两步,有了银行,并且将RMB存进去了。接下来我要买包子没钱,这时候就需要将RMB再取出来。但是存的时候我是在不同的网点存的,取的时候我想在手机银行APP上取,这第三步就是为了构建手机银行APP这个角色。
1 | ini复制代码IServiceProvider serviceProvider = services.BuildServiceProvider(); |
在这一步会引入一个新对象 ServiceProvider
,也就是给我们提供服务的对象,和 ServiceProviderEngine
,服务提供引擎,姑且这么叫吧。
1 | kotlin复制代码internal class DynamicServiceProviderEngine : CompiledServiceProviderEngine : ServiceProviderEngine |
仔细看下面这段源码(去除不相关部分),就是简单实例化一个 ServiceProvider
对象,ServiceProvider
对象包含一个 IServiceProviderEngine
属性,在 ServiceProvider
对象的构造函数内实例化并赋值。
1 | arduino复制代码public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) |
1 | c#复制代码private readonly IServiceProviderEngine _engine; |
ServiceProviderEngine
对象的内容比较多,由于上面代码只做了实例化,所以我们也只看与实例化相关的构造函数代码。
1 | c#复制代码internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory |
看了上面的构造函数,哇,又多了这么多新对象,无从下手是不是。这时候我们记住一点,RMB 存到了银行,所以我们就盯着银行:ServiceDescriptor
的动静,发现银行与 CallSiteFactory
这个对象有关联,CallSiteFactory
对象实例化需要银行(serviceDescriptors
)。
1 | scss复制代码CallSiteFactory = new CallSiteFactory(serviceDescriptors); |
1 | c#复制代码private readonly List<ServiceDescriptor> _descriptors; |
进入到 CallSiteFactory
对象内逻辑比较清晰,就是将我们银行内的 RMB(服务) 信息遍历并存储到相关字典集合内(_descriptorLookup
),给后续逻辑提供查找验证服务。
4、获取对象
上一步手机银行App的角色已经构建好了,这一步要开始取RMB了,取RMB需要什么?密码呗,这里的密码就是 IUserService
。
1 | ini复制代码var service = serviceProvider.GetService<IUserService>(); |
我们接下来看看取钱这一步微软都做了些什么操作,就是下面这段代码了:
1 | c#复制代码internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) |
一眼看去,有效代码其实就一行,涉及到三个对象:RealizedServices
、serviceType
、_createServiceAccessor
,都在上一步中出现过。
1 | ini复制代码RealizedServices.GetOrAdd(serviceType, _createServiceAccessor); |
1 | c#复制代码private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor = CreateServiceAccessor; |
我们将上面的代码发散一下,CreateServiceAccessor
就成了我们要研究的重点。
1 | c#复制代码var csa = CreateServiceAccessor(serviceType); |
1 | c#复制代码private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType) |
CreateServiceAccessor
方法内的有效代码是两行,我们一步步来深入:
1 | arduino复制代码CallSiteFactory.GetCallSite(serviceType, new CallSiteChain()); |
进入到 GetCallSite
方法内部,一层层剥离
1 | c#复制代码// 第一层 |
上面的代码都是围绕 ServiceCallSite
对象的创建再流转,依然是在为最后的取**RMB(服务)**做准备工作,我们注意一下这段代码:
1 | c#复制代码var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot); |
出现了一个熟悉的对象:Lifetime
,也就是我们注册服务的生命周期类型。
1 | c#复制代码public ResultCache(ServiceLifetime lifetime, Type type, int slot) |
到现在,准备工作的相关代码都已经走完了,下面就是最后一步:获取/构建对象
1 | kotlin复制代码return RealizeService(callSite); |
1 | c#复制代码protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite) |
1 | c#复制代码// singleton |
上面一段代码就是最终构建服务的代码了,前面的所有内容都是在为这一步做准备,具体代码构建的逻辑这篇文章就不讲了,有点技穷。看Transient:BuildTypeNoCache
方法内容,可以发现微软是通过 IL
去动态生成的服务。这只是里面的一种方式,还有另外一种方式大家可以自行去研究。
最后,写着写着就发现,有点把握不住。尽管在调式的时候对里面的一些代码的作用,以及怎么运转都有一些理解。但是写出来就不是那么回事,漏洞百出,索性贴出关键源码,记录一下这两天的研究成果。有条件的朋友可以自己去调式一遍源码,比看什么博客有效果多了。
我这里使用的是:JetBrains Rider
,调试源码比较方便,不用手动下载源码。
如果习惯了 vs
的同学可以去 github
上将源码下载下来通过 vs
去调试。
本文转载自: 掘金