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

科技   2024-11-04 08:04   广东  

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

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

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

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

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

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

备注:微信的排版你懂的,如需pc端完整的体验,可前往意社区查看

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创建CallSite ServiceCallSite? 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 disable    private 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.AnyKey var 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; //将生命周期和身份证,排序插槽包一层给ResultCache var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);

//如果实现类是一个实例,使用ConstantCallSite if (descriptor.HasImplementationInstance()) { callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.GetImplementationInstance()); } //如果实现类是一个key服务,并且没有实现工厂使用FactoryCallSite else 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); } //如果不是一个实例,而是一个类型,走CreateConstructorCallSite else 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); }
//如果参数有多个,去创建ArgumentCallSites parameterCallSites = 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;

//如果有多个构造函数,遍历每一个去CreateArgumentCallSites for (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);                    }
//如果参数有多个,去创建ArgumentCallSites parameterCallSites = 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 matches                        if (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的参数,调用了GetCallSite                if (!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大于0        parameters = 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简单吗?


.Net意社区,高频发布原创有深度的.Net相关知识内容

与你一起学习,一起进步

DotNet NB
.NET 技术学习分享,社区热点分享,专注为 .NET 社区做贡献,愿我们互相交流学习,共同推动社区发展
 最新文章