hello,大家好,今天仍然是橙子老哥的分享时间,希望大家一起学习,一起进步。
欢迎加入.net意社区,第一时间了解我们的动态,文章第一时间分享至社区
社区官方地址:https://ccnetcore.com
官方微信公众号:搜索 意.Net
添加橙子老哥微信加入官方微信群:chengzilaoge520
此篇我们继续接《.NetCore IOC依赖注入源码剖析(一)》,也是依赖注入的终章,解开最后的谜团,难度较高,需要一定时间钻研
终章
1、往期回顾
上期,我们介绍了依赖注入的使用,以及ServiceCollection 服务容器、ServiceProvider 这两大核心对象,细心的小伙伴们已经发现了
无论是在build的时候,还是在获取服务的时候,ServiceProvider 最终都会去调用CallSiteFactory
的GetCallSite
,通过GetCallSite
交给对应的ServiceProviderEngine
引擎中
以下是整个依赖注入调用链:
ServiceCollection -> (服务容器,将服务新增到容器中)ServiceProvider -> (服务提供者,服务的提供使用)CallSiteFactory-> (callsite工厂,用于创建对应的CallSite)CallSite-> (用于引擎所需要解析的服务配置信息)ServiceProviderEngine ->(通过CallSite具体去实例化对象的引擎)ServiceAccessor->(服务访问器,用于包装CallSite和实例委托缓存)object? result-> (通过服务访问器去执行委托实例需要的对象)
ServiceProvider 更像是又包了一层,对CallSite
和Engine
的一个封装,所以我们看不到具体它是如何创建对象,一切的未解之谜都丢到了这两个核心对象中
今天,我们将最后的谜团解开
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通过那种方式进行构建
现在我们走到了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个关键点,
- ConstructorInfo是哪里来的?
- 为什么可以通过ConstructorInfo.Invoke进行实例化?
问题1:我们往callsite进行回顾,callsite中去构造函数解析的时候,不是将构造函数反射一起塞到了callsite中吗?然后现在又传给了引擎,所以是callsite来的
问题2:ConstructorInfo是c#反射的一个类,可以通过构造函数和参数进行实例化,不清楚的小伙伴可以看看
这里也是这个引擎的真正核心的一点,拿callsite的ConstructorInfo和它解析的参数配置,做一个构造函数反射的Invoke,最终实例化出了我们想要的服务对象
5、总结
依赖注入的本质,递归构造函数参数+构造函数反射实例化
本篇难度较高,如果你能看到这,恭喜你,看到了这里。
我们回到前一节的开头,现在,你还敢说IOC简单吗?
div>div><div id="treeSkill">div>