【橙子老哥】.NetCore IOC依赖注入源码剖析(二)

server/2024/10/18 18:36:02/
<div id="article_content" class="article_content clearfix"><div id="content_views" class="markdown_views prism-atom-one-dark">display: none;">

hello,大家好,今天仍然是橙子老哥的分享时间,希望大家一起学习,一起进步。

欢迎加入.net意社区,第一时间了解我们的动态,文章第一时间分享至社区

社区官方地址:https://ccnetcore.com

官方微信公众号:搜索 意.Net

添加橙子老哥微信加入官方微信群:chengzilaoge520

此篇我们继续接《.NetCore IOC依赖注入源码剖析(一)》,也是依赖注入的终章,解开最后的谜团,难度较高,需要一定时间钻研

终章

1、往期回顾

上期,我们介绍了依赖注入的使用,以及ServiceCollection 服务容器、ServiceProvider 这两大核心对象,细心的小伙伴们已经发现了

无论是在build的时候,还是在获取服务的时候,ServiceProvider 最终都会去调用CallSiteFactoryGetCallSite,通过GetCallSite交给对应的ServiceProviderEngine 引擎中

以下是整个依赖注入调用链:
ServiceCollection  -> (服务容器,将服务新增到容器中)ServiceProvider -> (服务提供者,服务的提供使用)CallSiteFactory-> (callsite工厂,用于创建对应的CallSite)CallSite-> (用于引擎所需要解析的服务配置信息)ServiceProviderEngine ->(通过CallSite具体去实例化对象的引擎)ServiceAccessor->(服务访问器,用于包装CallSite和实例委托缓存)object? result-> (通过服务访问器去执行委托实例需要的对象)

ServiceProvider 更像是又包了一层,对CallSiteEngine 的一个封装,所以我们看不到具体它是如何创建对象,一切的未解之谜都丢到了这两个核心对象中

今天,我们将最后的谜团解开

2、CallSiteFactory - CallSite工厂构造函数

有了之前的分析,我们知道,主要是为了使用它的GetCallSite方法,去获取对应服务的配置信息,我们先看看它的构造函数

		//构造需要服务容器public CallSiteFactory(ICollection<ServiceDescriptor> descriptors){//堆栈守卫,c#一种数据结构,用于缓存_stackGuard = new StackGuard();//服务描述者,new一个新的服务描述者_descriptors = new ServiceDescriptor[descriptors.Count];//将传入的服务容器copy一份给自己descriptors.CopyTo(_descriptors, 0);//继续初始化Populate();}//这里的代码非常多,主要是为了校验泛型、非泛型的依赖注入类,是否存在问题,我们先去掉private void Populate(){//遍历所有的服务描述者foreach (ServiceDescriptor descriptor in _descriptors){Type serviceType = descriptor.ServiceType;//服务描述着,包一层,变成服务身份证var cacheKey = ServiceIdentifier.FromDescriptor(descriptor);//下面就是将服务身份证和ServiceDescriptor进行一个缓存操作//服务描述者查找器_descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem cacheItem);_descriptorLookup[cacheKey] = cacheItem.Add(descriptor);}}

以上就是构造函数做的事情,我们看到一个新的对象ServiceIdentifier服务身份证,这个就是用于找到对应服务描述者的,ServiceType 和ServiceKey 包一层,支持key服务依赖注入,为了保持唯一性

internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>{public object? ServiceKey { get; }public Type ServiceType { get; }public static ServiceIdentifier FromDescriptor(ServiceDescriptor serviceDescriptor){return new ServiceIdentifier(serviceDescriptor.ServiceKey, serviceDescriptor.ServiceType);}

我们再回到上面,构造函数中,并没有做什么,我们看看GetCallSite方法

3、CallSiteFactory - 获取CallSite方法(递归)

事先提个醒,从第一个方法开始,就准备开始递归套娃,请记住遇到的每个方法调用链

  private readonly ConcurrentDictionary<ServiceCacheKey, ServiceCallSite> _callSiteCache = new ConcurrentDictionary<ServiceCacheKey, ServiceCallSite>();internal ServiceCallSite? GetCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain) =>_callSiteCache.TryGetValue(new ServiceCacheKey(serviceIdentifier, DefaultSlot), out ServiceCallSite? site) ? site :CreateCallSite(serviceIdentifier, callSiteChain);

这个方法,又先包了一层缓存,如果不存在然后再执行CreateCallSite

  private ServiceCallSite? CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain){//堆栈守卫进行缓存判断if (!_stackGuard.TryEnterOnCurrentStack()){return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceIdentifier, callSiteChain);}//拿一个锁var callsiteLock = _callSiteLocks.GetOrAdd(serviceIdentifier, static _ => new object());lock (callsiteLock){//使用callSiteChain调用链,进行判断是否存在循环依赖注入callSiteChain.CheckCircularDependency(serviceIdentifier);//再包一层去TryCreateExact创建CallSiteServiceCallSite? callSite = TryCreateExact(serviceIdentifier, callSiteChain) ??//如果没有获取到,去找泛型获取操作TryCreateOpenGeneric(serviceIdentifier, callSiteChain) ??//泛型也没找到,再去找IEnumerable类型TryCreateEnumerable(serviceIdentifier, callSiteChain);return callSite;}}

以上方法,可以看到一个锁,这个是为了防止

  // C -> D -> A// E -> D -> A

当c和e执行的时候,到d和a就重复了,所以有个锁,另外,这里又多了一个对象,callSiteChain-callSite调用链,这个我们先留意一下,这里只是做了一个循环调用的校验,后面肯定又新增进去的对象

internal sealed class CallSiteChain{#nullable disableprivate readonly Dictionary<ServiceIdentifier, CallSiteChain.ChainItemInfo> _callSiteChain;public CallSiteChain(){this._callSiteChain = new Dictionary<ServiceIdentifier, CallSiteChain.ChainItemInfo>();}public void CheckCircularDependency(ServiceIdentifier serviceIdentifier){if (this._callSiteChain.ContainsKey(serviceIdentifier))throw new InvalidOperationException(this.CreateCircularDependencyExceptionMessage(serviceIdentifier));}public void Add(ServiceIdentifier serviceIdentifier, Type? implementationType = null){this._callSiteChain[serviceIdentifier] = new CallSiteChain.ChainItemInfo(this._callSiteChain.Count, implementationType);}}

这个调用链也很简单,就是一个字典,校验的时候,判断key中是否存在过,ok我们回到TryCreateExact方法,包了这么多层,我们还没有走进去

 private ServiceCallSite? TryCreateExact(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain){//_descriptorLookup 查找器进行缓存if (_descriptorLookup.TryGetValue(serviceIdentifier, out ServiceDescriptorCacheItem descriptor)){return TryCreateExact(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot);}//这里如果是key服务依赖注入,走这个,key注入if (serviceIdentifier.ServiceKey != null){// Check if there is a registration with KeyedService.AnyKeyvar catchAllIdentifier = new ServiceIdentifier(KeyedService.AnyKey, serviceIdentifier.ServiceType);if (_descriptorLookup.TryGetValue(catchAllIdentifier, out descriptor)){return TryCreateExact(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot);}}return null;}

这里主要是将key依赖注入,和普通的依赖注入进行一个区分,走了一下查找器的缓存,还是包了一层
最终走到TryCreateExact方法,我们再进去看看

 private ServiceCallSite? TryCreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot){if (serviceIdentifier.ServiceType == descriptor.ServiceType){//再来一个_callSiteCache缓存ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot);if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite)){return serviceCallSite;}ServiceCallSite callSite;//将生命周期和身份证,排序插槽包一层给ResultCachevar lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);//如果实现类是一个实例,使用ConstantCallSiteif (descriptor.HasImplementationInstance()){callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.GetImplementationInstance());}//如果实现类是一个key服务,并且没有实现工厂使用FactoryCallSiteelse if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null){callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);}//如果实现类是一个key服务,并且实现工厂使用FactoryCallSite 重载else if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null){callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, serviceIdentifier.ServiceKey!, descriptor.KeyedImplementationFactory);}//如果不是一个实例,而是一个类型,走CreateConstructorCallSiteelse if (descriptor.HasImplementationType()){callSite = CreateConstructorCallSite(lifetime, serviceIdentifier, descriptor.GetImplementationType()!, callSiteChain);}else{throw new InvalidOperationException(SR.InvalidServiceDescriptor);}//找到了,塞到_callSiteCache缓存callSite.Key = descriptor.ServiceKey;return _callSiteCache[callSiteKey] = callSite;}return null;}

TryCreateExact的代码就比较多了,主要是为了区分callsite通过那种方式进行构建

  1. ConstantCallSite (存在实例,例如单例模式,丢个实例)
  2. FactoryCallSite (key服务依赖注入
  3. ConstructorCallSite (通过类型依赖注入,构造函数)

现在我们走到了ConstructorCallSite ,这里的内容也是比较多的,我们省略一些非核心的,看看它是如何创建的

        private ConstructorCallSite CreateConstructorCallSite(ResultCache lifetime,ServiceIdentifier serviceIdentifier,[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,CallSiteChain callSiteChain){try{callSiteChain.Add(serviceIdentifier, implementationType);//记住这个,在这里将构造函数反射获取到ConstructorInfo[] constructors = implementationType.GetConstructors();//ConstructorCallSite 里面又包了很多个ServiceCallSite[]ServiceCallSite[]? parameterCallSites = null;//如果没有构造函数,直接报错if (constructors.Length == 0){throw new InvalidOperationException(SR.Format(SR.NoConstructorMatch, implementationType));}//如果构造函数的长度是一个,就是我们想要的else if (constructors.Length == 1){ConstructorInfo constructor = constructors[0];//构造函数去获取参数ParameterInfo[] parameters = constructor.GetParameters();//如果没有参数了,代表可以返回了,记住这个点,也是一个终结点if (parameters.Length == 0){//将上面反射获取到的构造函数信息,塞到ConstructorCallSite中,记住这里的构造函数return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor);}//如果参数有多个,去创建ArgumentCallSitesparameterCallSites = CreateArgumentCallSites(serviceIdentifier,implementationType,callSiteChain,parameters,throwIfCallSiteNotFound: true)!;//parameterCallSites塞给ConstructorCallSite一个重载return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites);}Array.Sort(constructors,(a, b) => b.GetParameters().Length.CompareTo(a.GetParameters().Length));ConstructorInfo? bestConstructor = null;HashSet<Type>? bestConstructorParameterTypes = null;//如果有多个构造函数,遍历每一个去CreateArgumentCallSitesfor (int i = 0; i < constructors.Length; i++){ParameterInfo[] parameters = constructors[i].GetParameters();ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites(serviceIdentifier,implementationType,callSiteChain,parameters,throwIfCallSiteNotFound: false);}}

在这里,我们关注到两个点:

                    if (parameters.Length == 0){//将上面反射获取到的构造函数信息,塞到ConstructorCallSite中,记住这里的构造函数return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor);}//如果参数有多个,去创建ArgumentCallSitesparameterCallSites = CreateArgumentCallSites(serviceIdentifier,implementationType,callSiteChain,parameters,throwIfCallSiteNotFound: true)!;//parameterCallSites塞给ConstructorCallSite一个重载return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites);

if (parameters.Length == 0) 明显是一个终止节点,而它的参数如何通过CreateArgumentCallSites创建的呢?我们再走进创建参数的地方

        private ServiceCallSite[]? CreateArgumentCallSites(ServiceIdentifier serviceIdentifier,Type implementationType,CallSiteChain callSiteChain,ParameterInfo[] parameters,bool throwIfCallSiteNotFound){//创建返回结果var parameterCallSites = new ServiceCallSite[parameters.Length];//遍历每一个参数for (int index = 0; index < parameters.Length; index++){ServiceCallSite? callSite = null;bool isKeyedParameter = false;Type parameterType = parameters[index].ParameterType;//这里遍历参数上是否有ServiceKeyAttribute的特性,用于key的依赖注入foreach (var attribute in parameters[index].GetCustomAttributes(true)){if (serviceIdentifier.ServiceKey != null && attribute is ServiceKeyAttribute){// Check if the parameter type matchesif (parameterType != serviceIdentifier.ServiceKey.GetType()){throw new InvalidOperationException(SR.InvalidServiceKeyType);}callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey);break;}if (attribute is FromKeyedServicesAttribute keyed){var parameterSvcId = new ServiceIdentifier(keyed.Key, parameterType);callSite = GetCallSite(parameterSvcId, callSiteChain);isKeyedParameter = true;break;}}//如果不是key的参数,调用了GetCallSiteif (!isKeyedParameter){callSite ??= GetCallSite(ServiceIdentifier.FromServiceType(parameterType), callSiteChain);}//给结果赋值parameterCallSites[index] = callSite;}return parameterCallSites;}

这里遍历了每一个参数,同时每个参数走的CallSiteFactory.GetCallSite方法,等等!什么?兜兜转转这么一大圈,又回到了GetCallSite,我们是在看GetCallSite去获取服务配置的方法,结果当它遍历构造函数参数的时候,参数的获取也是通过GetCallSite去获取的

所以,很明显,这是一个递归调用,根据类型获取服务配置,这个类型有构造函数,构造函数有很多参数,每个参数又根据类型进行创建!

如果是递归,终止条件是什么呢?其实上面也提过了,if (parameters.Length == 0) 当构造函数没有参数的时候,直接返回参数的callsite了

至此,已成闭环!整个调用链完成

4、ServiceProviderEngineScope-引擎实例对象

最后一个谜团,通过CallSiteFactory去创建了CallSite,只是将服务的信息配置存储在CallSite中,并未真正的去实例化对象,而做这一步的,就是服务提供者引擎,我们返回到ServiceProvider获取服务的地方(获取服务,肯定要实例化完成才获取的到)

        internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope){if (_disposed){ThrowHelper.ThrowObjectDisposedException();}ServiceAccessor serviceAccessor = _serviceAccessors.GetOrAdd(serviceIdentifier, _createServiceAccessor);OnResolve(serviceAccessor.CallSite, serviceProviderEngineScope);DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);object? result = serviceAccessor.RealizedService?.Invoke(serviceProviderEngineScope);System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));return result;}

这里最终创建实例,通过服务访问器,serviceAccessor.RealizedService?.Invoke中委托执行创建的,访问访问器我们上节已经讲过,是缓存了获取服务的过程,但是没有执行,这里我们可以看到Invoke塞入了我们的serviceProviderEngineScope引擎进去

我们接着上一节,ServiceAccessor中,在ServiceProvider初始化的时候,CreateServiceAccessor就已经将它赋值了,CreateServiceAccessor方法中,调用了引擎通过callSite去实例化

 Func<ServiceProviderEngineScope, object?> realizedService = _engine.RealizeService(callSite);

callSite,我们已经获取到了,这里本质是调用引擎的RealizeService方法,我们找到对应的引擎看看

    public override Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite){return (Func<ServiceProviderEngineScope, object>) (scope => CallSiteRuntimeResolver.Instance.Resolve(callSite, scope));}

这里也是包了跳转很多层,根据不同的callsite类型执行不同的方法,如果是构造函数,最终执行到

    protected override object VisitConstructor(ConstructorCallSite constructorCallSite,RuntimeResolverContext context){object[] parameters;//构造函数参数是0,我们不用去创建参数if (constructorCallSite.ParameterCallSites.Length == 0){parameters = Array.Empty<object>();}else{//如果ParameterCallSites大于0parameters = new object[constructorCallSite.ParameterCallSites.Length];//再包一层for (int index = 0; index < parameters.Length; ++index)parameters[index] = this.VisitCallSite(constructorCallSite.ParameterCallSites[index], context);}//直接通过ConstructorInfo,和 parameters 进行实例化return constructorCallSite.ConstructorInfo.Invoke(BindingFlags.DoNotWrapExceptions, (Binder) null, parameters, (CultureInfo) null);}

到这里,我们还要注意2个关键点,

  1. ConstructorInfo是哪里来的?
  2. 为什么可以通过ConstructorInfo.Invoke进行实例化?

问题1:我们往callsite进行回顾,callsite中去构造函数解析的时候,不是将构造函数反射一起塞到了callsite中吗?然后现在又传给了引擎,所以是callsite来的

问题2:ConstructorInfo是c#反射的一个类,可以通过构造函数和参数进行实例化,不清楚的小伙伴可以看看

这里也是这个引擎的真正核心的一点,拿callsite的ConstructorInfo和它解析的参数配置,做一个构造函数反射的Invoke,最终实例化出了我们想要的服务对象

5、总结

依赖注入的本质,递归构造函数参数+构造函数反射实例化

本篇难度较高,如果你能看到这,恭喜你,看到了这里。

我们回到前一节的开头,现在,你还敢说IOC简单吗?

div>div><div id="treeSkill">div>

http://www.ppmy.cn/server/131126.html

相关文章

量化之一:均值回归策略

文章目录 均值回归策略理论基础数学公式 关键指标简单移动平均线&#xff08;SMA&#xff09;标准差Z-Score 交易信号实际应用优缺点分析优点缺点 结论 实践backtrader参数&#xff1a;正常情况&#xff1a;异常情况&#xff1a; 均值回归策略 均值回归&#xff08;Mean Rever…

【动物识别系统】Python+卷积神经网络算法+人工智能+深度学习+机器学习+计算机课设项目+Django网页界面

一、介绍 动物识别系统。本项目以Python作为主要编程语言&#xff0c;并基于TensorFlow搭建ResNet50卷积神经网络算法模型&#xff0c;通过收集4种常见的动物图像数据集&#xff08;猫、狗、鸡、马&#xff09;然后进行模型训练&#xff0c;得到一个识别精度较高的模型文件&am…

linux下编译鸿蒙版curl、openssl

一.环境准备 1.参考说明 NDK开发介绍&#xff1a;https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/napi/ndk-development-overview.md 2.NDK下载 点击介绍页面中的链接可以跳转到相应下载页面&#xff1a; 下载相应版本&#xff1a; 下载完毕后解压到指定目…

C#中泛型的应用说明

一、泛型概述 泛型的主要目的是提高代码的复用性和类型安全性&#xff0c;通过使用泛型&#xff0c;可以创建处理任何数据类型的类和方法&#xff0c;而无需为每种数据类型都编写一个单独的类或方法。同时&#xff0c;泛型还可以在编译时提供类型检查&#xff0c;从而避免在运…

SeleniumBase在无头模式下绕过验证码的完整指南

概述 在现代Web爬虫技术中&#xff0c;SeleniumBase 是一款强大的自动化测试工具&#xff0c;能够模拟用户行为&#xff0c;进行高效的数据采集。然而&#xff0c;验证码&#xff08;CAPTCHA&#xff09;常常成为爬虫项目中的一个难题&#xff0c;尤其是在无头模式&#xff08…

HarmonyOS NEXT 应用开发实战(三、ArkUI页面底部导航TabBar的实现)

在开发HarmonyOS NEXT应用时&#xff0c;TabBar是用户界面设计中不可或缺的一部分。本文将通过代码示例&#xff0c;带领大家一同实现一个常用的TabBar&#xff0c;涵盖三个主要的内容页&#xff1a;首页、知乎日报和我的页面。以模仿知乎日报的项目为背景驱动&#xff0c;设定…

《机器学习与数据挖掘综合实践》实训课程教学解决方案

一、引言 随着信息技术的飞速发展&#xff0c;人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一&#xff0c;机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台&#xff0c;帮助学生掌握机器学习…

Go 语言中的格式化占位符

在 Go 语言中&#xff0c;fmt 包提供了大量的格式化占位符&#xff0c;用于格式化输出不同类型的数据。选择合适的占位符&#xff0c;可以确保输出的内容格式正确、清晰易懂。 常见的占位符&#xff1a; 基本类型 %v&#xff1a;按值的默认格式输出。适用于任何类型。%v&…