依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架。在《依赖注入[4]: 创建一个简易版的DI框架[上篇]》中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的设计和实现。
目录
一、服务注册:ServiceRegistry
二、DI容器:Cat
三、扩展方法
一、服务注册:ServiceRegistry
由于作为DI容器的Cat对象总是利用预先添加到服务注册来提供对应的服务实例,所以服务注册至关重要。如下所示的就是表示服务注册的ServiceRegistry的定义,它具有三个核心属性(ServiceType、Lifetime和Factory)分别代表服务类型、生命周期模式和用来创建服务实例的工厂。最终用来创建服务实例的工厂体现为一个类型为Func
public class ServiceRegistry { public Type ServiceType { get; } public Lifetime Lifetime { get; } public Func我们将针对同一个服务类型(ServiceType属性相同)的多个ServiceRegistry组成一个链表,作为相邻节点的两个ServiceRegistry对象通过Next属性关联起来。我们为ServiceRegistry定义了一个AsEnumerable方法是它返回由当前以及后续节点组成的ServiceRegistry集合。如果当前ServiceRegistry为链表表头,那么这个方法返回链表所有的节点。object> Factory { get; } internal ServiceRegistry Next { get; set; } public ServiceRegistry(Type serviceType, Lifetime lifetime, Func object> factory) { ServiceType = serviceType; Lifetime = lifetime; Factory = factory; } internal IEnumerable AsEnumerable() { var list = new List (); 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作为根容器的Cat对象通过_root字段表示。_registries字段返回的一个ConcurrentDictionary_registries; private ConcurrentDictionary object> _services; private ConcurrentBag _disposables; private volatile bool _disposed; public Cat() { _registries = new ConcurrentDictionary (); _root = this; _services = new ConcurrentDictionary object>(); _disposables = new ConcurrentBag (); } internal Cat(Cat parent) { _root = parent._root; _registries = _root._registries; _services = new ConcurrentDictionary object>(); _disposables = new ConcurrentBag (); } 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(ConcurrentDictionaryGetServiceCore方法只有在指定ServiceRegistry对应的服务实例不存在的情况下采用利用提供的工厂来创建服务实例,创建的服务实例会根据生命周期模式保存到作为根容器的Cat对象或者当前Cat对象上。如果提供的服务实例实现了IDisposable接口,在采用Root生命周期模式下会被保存到作为根容器的Cat对象的待释放列表中,如果生命周期模式为Self,它会被添加到当前Cat对象的待释放列表中。object> services, ConcurrentBag 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
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
public static class CatExtensions { public static Cat Register(this Cat cat, Type from, Type to, Lifetime lifetime) { Func第三个扩展方法来指定的是一个用来提供服务实例的Funcobject> factory = (_, arguments) => Create(_, to, arguments); cat.Register(new ServiceRegistry(from, lifetime, factory)); return cat; } public static Cat Register (this Cat cat, Lifetime lifetime) where TTo:TFrom => cat. Register(typeof(TFrom), typeof(TTo), lifetime); public static Cat Register (this Cat cat, TServiceType instance) { Func object> factory = (_, arguments) => instance; cat.Register(new ServiceRegistry(typeof(TServiceType), Lifetime.Root, factory)); return cat; } public static Cat Register (this Cat cat, Func factory, Lifetime lifetime) { cat.Register(new ServiceRegistry(typeof(TServiceType), lifetime, (_,arguments)=>factory(_))); return cat; } public static bool HasRegistry (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 ().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
[AttributeUsage( AttributeTargets.Constructor)] public class InjectionAttribute: Attribute {}上面给出的代码片段还提供了两个HasRegistry和HasRegistry
public static class CatExtensions { public static IEnumerableGetServices (this Cat cat) => cat.GetService >(); public static T GetService (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框架[服务消费]