為了讓讀者朋友們能夠對.NET Core DI框架的實現原理具有一個深刻而認識,我們采用與之類似的設計構架了一個名為Cat的DI框架。在《依賴注入[4]: 創建一個簡易版的DI框架[上篇]》中我們介紹了Cat的基本編程模式,接下來我們就來聊聊Cat的設計和實現。
目錄
一、服務注冊:ServiceRegistry
二、DI容器:Cat
三、擴展方法
一、服務注冊:ServiceRegistry
由於作為DI容器的Cat對象總是利用預先添加到服務注冊來提供對應的服務實例,所以服務注冊至關重要。如下所示的就是表示服務注冊的ServiceRegistry的定義,它具有三個核心屬性(ServiceType、Lifetime和Factory)分別代表服務類型、生命周期模式和用來創建服務實例的工廠。最終用來創建服務實例的工廠體現為一個類型為Func<Cat,Type[], object>的委托對象,它具有的兩個輸入分別代表當前使用的Cat對象以及提供服務類型的泛型參數列表,如果提供的服務類型並不是一個泛型類型,這個參數會指定為空的類型數組。
public class ServiceRegistry { public Type ServiceType { get; } public Lifetime Lifetime { get; } public Func<Cat,Type[], object> Factory { get; } internal ServiceRegistry Next { get; set; } public ServiceRegistry(Type serviceType, Lifetime lifetime, Func<Cat,Type[], object> factory) { ServiceType = serviceType; Lifetime = lifetime; Factory = factory; } internal IEnumerable<ServiceRegistry> AsEnumerable() { var list = new List<ServiceRegistry>(); for (var self = this; self!=null; self= self.Next) { list.Add(self); } return list; } }
二、DI容器:Cat
在了解了表示服務注冊的ServiceRegistry之后,我們來着重介紹表示DI容器的Cat類型。如下面的代碼片段所示,Cat同時實現了IServiceProvider和IDisposable接口,定義在前者中的GetService用於提供最終的服務實例。作為根容器的Cat對象通過公共構造函數創建,另一個內部構造函數則用來創建作為子容器的Cat對象,指定的Cat對象將作為父容器。
public class Cat : IServiceProvider, IDisposable { internal Cat _root; internal ConcurrentDictionary<Type, ServiceRegistry> _registries; private ConcurrentDictionary<ServiceRegistry, object> _services; private ConcurrentBag<IDisposable> _disposables; private volatile bool _disposed; public Cat() { _registries = new ConcurrentDictionary<Type, ServiceRegistry>(); _root = this; _services = new ConcurrentDictionary<ServiceRegistry, object>(); _disposables = new ConcurrentBag<IDisposable>(); } internal Cat(Cat parent) { _root = parent._root; _registries = _root._registries; _services = new ConcurrentDictionary<ServiceRegistry, object>(); _disposables = new ConcurrentBag<IDisposable>(); } private void EnsureNotDisposed() { if (_disposed) { throw new ObjectDisposedException("Cat"); } } ... }
雖然我們為Cat定義了若干擴展方法來提供多種不同的服務注冊,但是這些方法最終都會調用如下這個Register方法,該方法會將提供的ServiceRegistry添加到_registries字段表示的字典對象中。值得注意的是,不論我們是調用那個Cat對象的Register方法,指定的ServiceRegistry都會被添加到作為根容器的Cat對象上。
public class Cat : IServiceProvider, IDisposable { public Cat Register(ServiceRegistry registry) { EnsureNotDisposed(); if (_registries.TryGetValue(registry.ServiceType, out var existing)) { _registries[registry.ServiceType] = registry; registry.Next = existing; } else { _registries[registry.ServiceType] = registry; } return this; } ... }
用來提供服務實例的核心操作實現在如下這個GetServiceCore方法中。如下面的代碼片段所示,我們在調用該方法的時候需要指定對應的ServiceRegistry和服務對象泛型參數。當該方法被執行的時候,對於Transient生命周期模式,它會直接利用ServiceRegistry提供的工廠來創建服務實例,如果服務實例實現了IDisposable接口,它會被添加到_disposables字段表示的待釋放服務實例列表中。對於Root和Self生命周期模式,該方法會先根據提供的ServiceRegistry判斷是否對應的服務實例已經存在,存在的服務實例會直接作為返回值。
public class Cat : IServiceProvider, IDisposable { private object GetServiceCore(ServiceRegistry registry, Type[] genericArguments) { var serviceType = registry.ServiceType; object GetOrCreate(ConcurrentDictionary<ServiceRegistry, object> services, ConcurrentBag<IDisposable> disposables) { if (services.TryGetValue(registry, out var service)) { return service; } service = registry.Factory(this, genericArguments); services[registry] = service; var disposable = service as IDisposable; if (null != disposable) { disposables.Add(disposable); } return service; } switch (registry.Lifetime) { case Lifetime.Root: return GetOrCreate(_root._services, _root._disposables); case Lifetime.Self: return GetOrCreate(_services, _disposables); default: { var service = registry.Factory(this, genericArguments); var disposable = service as IDisposable; if (null != disposable) { _disposables.Add(disposable); } return service; } } } }
在實現的GetService方法中,Cat會根據指定的服務類型找到對應的ServiceRegistry對象,並最終調用GetServiceCore方法來提供對應的服務實例。GetService方法會解決一些特殊服務提供問題,如果服務類型為Cat或者IServiceProvider,該方法返回的就是它自己。如果服務類型為IEnumerable<T>,GetService會根據泛型參數類型T找到所有的ServiceRegistry並利用它們來創建對應的服務實例,最終返回的是有這些服務實例組成的集合。除了這些,針對泛型服務實例的提供也是在這個方法中解決的。
public class Cat : IServiceProvider, IDisposable { public object GetService(Type serviceType) { EnsureNotDisposed(); if (serviceType == typeof(Cat) || serviceType == typeof(IServiceProvider)) { return this; } ServiceRegistry registry; if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { var elementType = serviceType.GetGenericArguments()[0]; if (!_registries.TryGetValue(elementType, out registry)) { return Array.CreateInstance(elementType, 0); } var registries = registry.AsEnumerable(); var services = registries.Select(it => GetServiceCore(it, new Type[0])).ToArray(); Array array = Array.CreateInstance(elementType, services.Length); services.CopyTo(array, 0); return array; } if (serviceType.IsGenericType && !_registries.ContainsKey(serviceType)) { var definition = serviceType.GetGenericTypeDefinition(); return _registries.TryGetValue(definition, out registry) ? GetServiceCore(registry, serviceType.GetGenericArguments()) : null; } return _registries.TryGetValue(serviceType, out registry) ? GetServiceCore(registry, new Type[0]) : null; } ... }
在實現的Dispose方法中,由於所有待釋放的服務實例已經保存到_disposables字段表示的集合中,所以我們只需要依次調用它們的Dispose方法即可。在釋放了所有服務實例並清空待釋放列表后,Dispose方法還會清空_services字段表示的服務實例列表。
public class Cat : IServiceProvider, IDisposable { public void Dispose() { _disposed = true; foreach(var disposable in _disposables) { disposable.Dispose(); } while (!_disposables.IsEmpty) { _disposables.TryTake(out _); } _services.Clear(); } ... }
三、擴展方法
為了方便注冊服務,我們定義了如下三個4個擴展方法Register。由於服務注冊的添加總是需要調用Cat自身的Register方法來完成,所以這些方法最終都需要創建一個代表服務注冊的ServiceRegistry對象。對於一個ServiceRegistry對象來說,它最為核心的莫過於表示服務實例創建工廠的Func<Cat,Type[], object>對象,所以上述這4個擴展方法需要解決的就是創建這么一個委托對象。
public static class CatExtensions { public static Cat Register(this Cat cat, Type from, Type to, Lifetime lifetime) { Func<Cat, Type[], object> factory = (_, arguments) => Create(_, to, arguments); cat.Register(new ServiceRegistry(from, lifetime, factory)); return cat; } public static Cat Register<TFrom, TTo>(this Cat cat, Lifetime lifetime) where TTo:TFrom => cat. Register(typeof(TFrom), typeof(TTo), lifetime); public static Cat Register<TServiceType>(this Cat cat, TServiceType instance) { Func<Cat, Type[], object> factory = (_, arguments) => instance; cat.Register(new ServiceRegistry(typeof(TServiceType), Lifetime.Root, factory)); return cat; } public static Cat Register<TServiceType>(this Cat cat, Func<Cat,TServiceType> factory, Lifetime lifetime) { cat.Register(new ServiceRegistry(typeof(TServiceType), lifetime, (_,arguments)=>factory(_))); return cat; } public static bool HasRegistry<T>(this Cat cat) => cat.HasRegistry(typeof(T)); public static bool HasRegistry(this Cat cat, Type serviceType) => cat._root._registries.ContainsKey(serviceType); private static object Create(Cat cat, Type type, Type[] genericArguments) { if (genericArguments.Length > 0) { type = type.MakeGenericType(genericArguments); } var constructors = type.GetConstructors(BindingFlags.Instance); if (constructors.Length == 0) { throw new InvalidOperationException($"Cannot create the instance of {type} which does not have an public constructor."); } var constructor = constructors.FirstOrDefault(it => it.GetCustomAttributes(false).OfType<InjectionAttribute>().Any()); constructor = constructor ?? constructors.First(); var parameters = constructor.GetParameters(); if (parameters.Length == 0) { return Activator.CreateInstance(type); } var arguments = new object[parameters.Length]; for (int index = 0; index < arguments.Length; index++) { var parameter = parameters[index]; var parameterType = parameter.ParameterType; if (cat.HasRegistry(parameterType)) { arguments[index] = cat.GetService(parameterType); } else if (parameter.HasDefaultValue) { arguments[index] = parameter.DefaultValue; } else { throw new InvalidOperationException($"Cannot create the instance of {type} whose constructor has non-registered parameter type(s)"); } } return Activator.CreateInstance(type, arguments); } }
我們刻意簡化了構造函數的篩選邏輯。為了解決構造函數的選擇問題,我們引入如下這個InjectionAttribute特性。我們將所有公共實例構造函數作為候選的構造函數,並會優先選擇標注了該特性的構造函數。當構造函數被選擇出來后,我們需要通過分析其參數類型並利用Cat對象來提供具體的參數值,這實際上是一個遞歸的過程。最終我們將針對構造函數的調用轉換成Func<Cat,Type[], object>對象,進而創建出表示服務注冊的ServiceRegistry對象。
[AttributeUsage( AttributeTargets.Constructor)] public class InjectionAttribute: Attribute {}
public static class CatExtensions { public static IEnumerable<T> GetServices<T>(this Cat cat) => cat.GetService<IEnumerable<T>>(); public static T GetService<T>(this Cat cat) => (T)cat.GetService(typeof(T)); }
依賴注入[1]: 控制反轉
依賴注入[2]: 基於IoC的設計模式
依賴注入[3]: 依賴注入模式
依賴注入[4]: 創建一個簡易版的DI框架[上篇]
依賴注入[5]: 創建一個簡易版的DI框架[下篇]
依賴注入[6]: .NET Core DI框架[編程體驗]
依賴注入[7]: .NET Core DI框架[服務注冊]
依賴注入[8]: .NET Core DI框架[服務消費]