.NET Core基础篇之:依赖注入DependencyInjection
依赖注入已经不是什么新鲜话题了,在.NET Framework时期就已经出现了各种依赖注入框架,比如:autofac、unity等。只是在.net core微软将它搬上了台面,不用再依赖第三方组件(那是不可能的)。依赖注入的概念与为什么选择使用依赖注入这里就不说了,网上搜一下就会有各种答案,今天这里的内容是看看在.net core中,简单实用的依赖注入,背后到底做了哪些操作。
创建项目
今天演示不采用asp.net core项目,而是采用.net core控制台。相对前者,后者的操作逻辑更加完整简洁。
准备接口与对象,老User了
public class UserService : IUserService
{
public string getName()
{
return "my name is dotnetboy";
}
}
public interface IUserService
{
string getName();
}
/// <summary>
/// 入口方法
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
// 1、实例化服务容器
IServiceCollection services = new ServiceCollection();
// 2、添加服务
services.AddTransient<IUserService, UserService>();
// 3、构建服务提供对象
IServiceProvider serviceProvider = services.BuildServiceProvider();
// 4、解析并获取服务对象
var service = serviceProvider.GetService<IUserService>();
// 5、调用
Console.WriteLine(service.getName());
}
1、实例化服务容器
IServiceCollection services = new ServiceCollection();
这里出现了一个新对象:IServiceCollectionService,也就是startup中的
public void ConfigureServices(IServiceCollection services){}
F12 可以看到,IServiceCollectionService 继承了IList<ServiceDescriptor>接口,又引申出 ServiceDescriptor 对象。
//
// 摘要:
// Specifies the contract for a collection of service descriptors.
public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
{
}
而 ServiceDescriptor 对象见名知意就知道是用来描述服务信息的对象了。ServiceDescriptor 对象的内容有点多,在这里我们暂时只需要了解三个:
/// <summary>
/// Describes a service with its service type, implementation, and lifetime.
/// </summary>
[DebuggerDisplay("Lifetime = {Lifetime}, ServiceType = {ServiceType}, ImplementationType = {ImplementationType}")]
public class ServiceDescriptor
{
/// 生命周期
public ServiceLifetime Lifetime { get; }
/// 服务对象
public Type ServiceType { get; }
/// 服务实现对象
public Type ImplementationType { get; }
}
Lifetime:生命周期
SericeType:服务对象
ImplementationType:服务实现对象
第一步内容比较简单,就是声明一个服务容器集合。我们可以把 IServiceCollection 比做成银行,把服务比喻成 RMB ,现在银行有了,我们下一步肯定就是存 RMB 进去了。
2、添加服务
上面我们提到了 ServiceDescriptor 对象的三个属性:Lifetime、ServiceType、ImplementationType。
再回过头看 services.AddTransient<IUserService, UserService>(); 这段代码
AddTransient 指定了
Lifetime,也就是TransientIUserService 表示
ServiceTypeUserService 表示
ImplementationType
下面是 AddTransient 相关的源码,很是直观明了。就是将 RMB 存储银行,到底是 活期、定期还是理财就由开发者自己去定义了。
public static IServiceCollection AddTransient(
this IServiceCollection services,
Type serviceType,
Type implementationType)
{
......
return Add(services, serviceType, implementationType, ServiceLifetime.Transient);
}
private static IServiceCollection Add(
IServiceCollection collection,
Type serviceType,
Type implementationType,
ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
3、构建服务提供对象
上面两步,有了银行,并且将RMB存进去了。接下来我要买包子没钱,这时候就需要将RMB再取出来。但是存的时候我是在不同的网点存的,取的时候我想在手机银行APP上取,这第三步就是为了构建手机银行APP这个角色。
IServiceProvider serviceProvider = services.BuildServiceProvider();
在这一步会引入一个新对象 ServiceProvider ,也就是给我们提供服务的对象,和 ServiceProviderEngine ,服务提供引擎,姑且这么叫吧。
internal class DynamicServiceProviderEngine : CompiledServiceProviderEngine : ServiceProviderEngine
仔细看下面这段源码(去除不相关部分),就是简单实例化一个 ServiceProvider 对象,ServiceProvider 对象包含一个 IServiceProviderEngine 属性,在 ServiceProvider 对象的构造函数内实例化并赋值。
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
......
return new ServiceProvider(services, options);
}
private readonly IServiceProviderEngine _engine;
// serviceDescriptors:服务集合,options:服务提供者类型
internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
IServiceProviderEngineCallback callback = null;
switch (options.Mode)
{
case ServiceProviderMode.Default:
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
}
}
ServiceProviderEngine 对象的内容比较多,由于上面代码只做了实例化,所以我们也只看与实例化相关的构造函数代码。
internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
{
private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor;
internal ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> RealizedServices { get; }
public ServiceProviderEngineScope Root { get; }
public IServiceScope RootScope => Root;
protected CallSiteRuntimeResolver RuntimeResolver { get; }
internal CallSiteFactory CallSiteFactory { get; }
protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback)
{
_createServiceAccessor = CreateServiceAccessor;
Root = new ServiceProviderEngineScope(this);
RuntimeResolver = new CallSiteRuntimeResolver();
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());
RealizedServices = new ConcurrentDictionary<Type,Func<ServiceProviderEngineScope, object>>();
}
}
看了上面的构造函数,哇,又多了这么多新对象,无从下手是不是。这时候我们记住一点,RMB 存到了银行,所以我们就盯着银行:ServiceDescriptor 的动静,发现银行与 CallSiteFactory 这个对象有关联,CallSiteFactory 对象实例化需要银行(serviceDescriptors)。
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());
private readonly List<ServiceDescriptor> _descriptors;
private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new Dictionary<Type, ServiceDescriptorCacheItem>();
private readonly StackGuard _stackGuard;
public CallSiteFactory(IEnumerable<ServiceDescriptor> descriptors)
{
_stackGuard = new StackGuard();
_descriptors = descriptors.ToList();
Populate();
}
private void Populate()
{
foreach (var descriptor in _descriptors)
{
var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
......
var cacheKey = descriptor.ServiceType;
_descriptorLookup.TryGetValue(cacheKey, out var cacheItem);
_descriptorLookup[cacheKey] = cacheItem.Add(descriptor);
}
}
进入到 CallSiteFactory 对象内逻辑比较清晰,就是将我们银行内的 RMB(服务) 信息遍历并存储到相关字典集合内(_descriptorLookup),给后续逻辑提供查找验证服务。
4、获取对象
上一步手机银行App的角色已经构建好了,这一步要开始取RMB了,取RMB需要什么?密码呗,这里的密码就是 IUserService。
var service = serviceProvider.GetService<IUserService>();
我们接下来看看取钱这一步微软都做了些什么操作,就是下面这段代码了:
internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
{
......
// 已经实现的服务
var realizedService = RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
// _callback?.OnResolve(serviceType, serviceProviderEngineScope);
......
return realizedService.Invoke(serviceProviderEngineScope);
}
一眼看去,有效代码其实就一行,涉及到三个对象:RealizedServices、serviceType、_createServiceAccessor,都在上一步中出现过。
RealizedServices.GetOrAdd(serviceType, _createServiceAccessor);
private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor = CreateServiceAccessor;
internal ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> RealizedServices { get; }
我们将上面的代码发散一下,CreateServiceAccessor 就成了我们要研究的重点。
var csa = CreateServiceAccessor(serviceType);
RealizedServices.GetOrAdd(serviceType, csa);
private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
{
var callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
if (callSite != null)
{
......
return RealizeService(callSite);
}
return _ => null;
}
CreateServiceAccessor 方法内的有效代码是两行,我们一步步来深入:
CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
进入到 GetCallSite 方法内部,一层层剥离
// 第一层
internal ServiceCallSite GetCallSite(Type serviceType, CallSiteChain callSiteChain)
{
return _callSiteCache.GetOrAdd(serviceType, type => CreateCallSite(type, callSiteChain));
}
// 第二层
private ServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
{
......
var callSite = TryCreateExact(serviceType, callSiteChain) ??
TryCreateOpenGeneric(serviceType, callSiteChain) ??
TryCreateEnumerable(serviceType, callSiteChain);
_callSiteCache[serviceType] = callSite;
return callSite;
}
// 第三层
private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
{
if (serviceType == descriptor.ServiceType)
{
ServiceCallSite callSite;
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
......
new ServiceCallSite(......);
return callSite;
}
return null;
}
上面的代码都是围绕 ServiceCallSite 对象的创建再流转,依然是在为最后的取RMB(服务)做准备工作,我们注意一下这段代码:
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
出现了一个熟悉的对象:Lifetime,也就是我们注册服务的生命周期类型。
public ResultCache(ServiceLifetime lifetime, Type type, int slot)
{
switch (lifetime)
{
case ServiceLifetime.Singleton:
Location = CallSiteResultCacheLocation.Root;
break;
case ServiceLifetime.Scoped:
Location = CallSiteResultCacheLocation.Scope;
break;
case ServiceLifetime.Transient:
Location = CallSiteResultCacheLocation.Dispose;
break;
default:
Location = CallSiteResultCacheLocation.None;
break;
}
Key = new ServiceCacheKey(type, slot);
}
public CallSiteResultCacheLocation Location { get; set; }
public ServiceCacheKey Key { get; set; }
到现在,准备工作的相关代码都已经走完了,下面就是最后一步:获取/构建对象
return RealizeService(callSite);
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
{
var realizedService = ResolverBuilder.Build(callSite);
RealizedServices[callSite.ServiceType] = realizedService;
return realizedService;
}
// singleton
public Func<ServiceProviderEngineScope, object> Build(ServiceCallSite callSite)
{
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
var value = _runtimeResolver.Resolve(callSite, _rootScope);
return scope => value;
}
return BuildType(callSite).Lambda;
}
// Scoped
private GeneratedMethod BuildType(ServiceCallSite callSite)
{
if (callSite.Cache.Location == CallSiteResultCacheLocation.Scope)
{
return _scopeResolverCache.GetOrAdd(callSite.Cache.Key, _buildTypeDelegate, callSite);
}
return BuildTypeNoCache(callSite);
}
// Transient
private GeneratedMethod BuildTypeNoCache(ServiceCallSite callSite)
{
var dynamicMethod = new DynamicMethod("ResolveService",
attributes: MethodAttributes.Public | MethodAttributes.Static,
callingConvention: CallingConventions.Standard,
returnType: typeof(object),
parameterTypes: new[] { typeof(ILEmitResolverBuilderRuntimeContext), typeof(ServiceProviderEngineScope) },
owner: GetType(),
skipVisibility: true);
var ilGenerator = dynamicMethod.GetILGenerator(512);
......
}
上面一段代码就是最终构建服务的代码了,前面的所有内容都是在为这一步做准备,具体代码构建的逻辑这篇文章就不讲了,有点技穷。看Transient:BuildTypeNoCache方法内容,可以发现微软是通过 IL 去动态生成的服务。这只是里面的一种方式,还有另外一种方式大家可以自行去研究。
最后,写着写着就发现,有点把握不住。尽管在调式的时候对里面的一些代码的作用,以及怎么运转都有一些理解。但是写出来就不是那么回事,漏洞百出,索性贴出关键源码,记录一下这两天的研究成果。有条件的朋友可以自己去调式一遍源码,比看什么博客有效果多了。
我这里使用的是:JetBrains Rider ,调试源码比较方便,不用手动下载源码。
如果习惯了 vs 的同学可以去 github 上将源码下载下来通过 vs 去调试。
.NET Core基础篇之:依赖注入DependencyInjection的更多相关文章
- [ASP.NET Core开发实战]基础篇02 依赖注入
ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ...
- [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...
- 大比速:remoting、WCF(http)、WCF(tcp)、WCF(RESTful)、asp.net core(RESTful) .net core 控制台程序使用依赖注入(Autofac)
大比速:remoting.WCF(http).WCF(tcp).WCF(RESTful).asp.net core(RESTful) 近来在考虑一个服务选型,dotnet提供了众多的远程服务形式.在只 ...
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- ASP.NET Core技术研究-探秘依赖注入框架
ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务.提供服务.依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者. 毫不夸张的说,ASP.NET ...
- Asp.Net Core 3.1学习-依赖注入、服务生命周期(6)
1.前言 面向对象设计(OOD)里有一个重要的思想就是依赖倒置原则(DIP),并由该原则牵引出依赖注入(DI).控制反转(IOC)及其容器等概念.在学习Core依赖注入.服务生命周期之前,下面让我们先 ...
- ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法
在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...
- [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
<服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现.在过去的多个版 ...
随机推荐
- k8s学习笔记(3)- kubectl高可用部署,扩容,升级,回滚springboot应用
前言:上一篇通过rancher管理k8s,部署服务应用扩容,高可用,本篇介绍kubectl命令行部署高可用集群节点,测试升级.扩容等 1.测试环境:3节点k3s,使用其中2节点(ubuntunode1 ...
- 【Spring】重新认识 IoC
前言 IoC (Inversion of control) 并不是Spring特有的概念. IoC 维基百科的解释: In software engineering, inversion of con ...
- 【UE4 C++】Tick的三种方式、异步蓝图节点
Tick的三种方式 包括 默认 Tick (Actor.Component.UMG) TimerManager 定时器 FTickableGameObject 可以写原生 Object 也可以继承UO ...
- vue3.x自定义组件双向数据绑定v-model
vue2.x 语法 在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件: <ChildComponent v-model="pag ...
- Java中的函数式编程(七)流Stream的Map-Reduce操作
写在前面 Stream 的 Map-Reduce 操作是Java 函数式编程的精华所在,同时也是最为复杂的部分.但一旦你啃下了这块硬骨头,那你就真正熟悉Java的函数式编程了. 如果你有大数据的编程经 ...
- 【二食堂】Beta - Scrum Meeting 2
Scrum Meeting 2 例会时间:5.14 18:30~18:50 进度情况 组员 当前进度 今日任务 李健 1. 还在进行摸索,目前做出了一个demo可以进行简单的划词 issue 1. 继 ...
- 2020BUAA软工提问回顾和个人总结作业
2020BUAA软工提问回顾和个人总结作业 17373010 杜博玮 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 提问回顾和个人总结作业 我在 ...
- BZOJ4919[Lydsy1706月赛]大根堆-------------线段树进阶
是不是每做道线段树进阶都要写个题解..根本不会写 Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切 ...
- series和读取外部数据
1.为什么学习pandas 我们并不是不愿意学习新的知识,只是在学习之前我们更想知道学习他们能够帮助我们解决什么问题.--伟哥 numpy虽然能够帮助我们处理数值,但是pandas除了处理数值之外(基 ...
- js this指向汇总
this指向 普通函数 window 定时器函数 window 事件函数 事件源 箭头函数 父function中的this,没有就是window 对象函数 对象本身 构造函数 实例化 ...