依賴注入已經不是什麼新鮮話題了,在.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
,也就是Transient
-
IUserService 表示
ServiceType
-
UserService 表示
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
去除錯。