依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.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框架[服务消费]