依赖注入[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;
}
}
我们将针对同一个服务类型(ServiceType属性相同)的多个ServiceRegistry组成一个链表,作为相邻节点的两个ServiceRegistry对象通过Next属性关联起来。我们为ServiceRegistry定义了一个AsEnumerable方法是它返回由当前以及后续节点组成的ServiceRegistry集合。如果当前ServiceRegistry为链表表头,那么这个方法返回链表所有的节点。
二、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对象通过_root字段表示。_registries字段返回的一个ConcurrentDictionary<Type, ServiceRegistry>对象表示所有添加的服务注册,字典对象的Key和Value分别表示服务类型和ServiceRegistry链表。由当前Cat对象提供的非Transient服务实例保存在由_services字段表示的一个ConcurrentDictionary<ServiceRegistry, object>对象上,该字典对象的Key表示创建服务实例所使用的ServiceRegistry对象。由于需要负责完成对提供服务实例的释放工作,所以我们需要将实现了IDisposable接口的服务实例保存在通过_disposables字段表示的集合中。
虽然我们为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;
}
}
}
}
GetServiceCore方法只有在指定ServiceRegistry对应的服务实例不存在的情况下采用利用提供的工厂来创建服务实例,创建的服务实例会根据生命周期模式保存到作为根容器的Cat对象或者当前Cat对象上。如果提供的服务实例实现了IDisposable接口,在采用Root生命周期模式下会被保存到作为根容器的Cat对象的待释放列表中,如果生命周期模式为Self,它会被添加到当前Cat对象的待释放列表中。
在实现的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);
}
}
第三个扩展方法来指定的是一个用来提供服务实例的Func<Cat,TServiceType>对象,最后一个扩展方法指定的直接就是服务实例,所以我们很容易将提供的参数转换成一个Func<Cat,Type[], object>。由于前两个重载指定的是服务实现类型,所以我们需要调用对应的构造函数来创建服务实例,这一逻辑实现在私有的Create方法中。
我们刻意简化了构造函数的筛选逻辑。为了解决构造函数的选择问题,我们引入如下这个InjectionAttribute特性。我们将所有公共实例构造函数作为候选的构造函数,并会优先选择标注了该特性的构造函数。当构造函数被选择出来后,我们需要通过分析其参数类型并利用Cat对象来提供具体的参数值,这实际上是一个递归的过程。最终我们将针对构造函数的调用转换成Func<Cat,Type[], object>对象,进而创建出表示服务注册的ServiceRegistry对象。
[AttributeUsage( AttributeTargets.Constructor)]
public class InjectionAttribute: Attribute {}
上面给出的代码片段还提供了两个HasRegistry和HasRegistry<T>方法来确定指定类型的服务注册是否存在。除此之外,用于提供服务实例的泛型方法GetService<T>和用于提供所有指定类型服务实例的GetService<T>方法采用了如下的定义方式。
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框架[服务消费]
依赖注入[5]: 创建一个简易版的DI框架[下篇]的更多相关文章
- .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...
- 依赖注入[4]: 创建一个简易版的DI框架[上篇]
本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...
- .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]
原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- 使用 js 实现一个简易版的 vue 框架
使用 js 实现一个简易版的 vue 框架 具有挑战性的前端面试题 refs https://www.infoq.cn/article/0NUjpxGrqRX6Ss01BLLE xgqfrms 201 ...
- 实现一个简易版的SpringMvc框架
先来看我们的程序入口DispatcherServlet 主要核心处理流程如下: 1 扫描基础包下的带有controller 以及 service注解的class,并保存到list中 2 对第一步扫描到 ...
- 如何实现一个简易版的 Spring - 如何实现 Setter 注入
前言 之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了...相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离 ...
- 如何实现一个简易版的 Spring - 如何实现 Constructor 注入
前言 本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功 ...
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...
随机推荐
- Linux定时执行PHP
1.使用crond服务 crontab -e #编辑任务列表 crontab -l #展示任务列表 26 15 * * * /usr/local/php70/bin/php -q /data/www/ ...
- 数据库六大约束用法:主键(primary key)、外键(foreign key)、非空(not null)、默认(default)、检查(check)、唯一(unique)
1. 数据库有六大约束 主键(primary key) 外键(foreign key):被参照的键必须有唯一约束或是主键 非空(not null) 默认(default) 检查(check):orac ...
- https请求之绕过证书安全校验工具类(原)
package com.isoftstone.core.util; import java.io.BufferedReader; import java.io.ByteArrayOutputStrea ...
- 企业面试必会shell
企业面试题1: 使用for循环在/oldboy目录下通过随机小写10个字母加固定字符串oldboy批量创建10个html文件,名称例如为: [root@oldboy oldboy]# sh /serv ...
- PHP消息队列实现及应用
目前对消息队列并不了解其原理,本篇文章主要是通过慕课网学习归纳的一些笔记,为后续学习打下基础. 众所周知在对网站设计的时候,会遇到给用户“群发短信”,“订单系统有大量的日志”,“秒杀设计”等,服务器没 ...
- WPF管理系统自定义分页控件 - WPF特工队内部资料
最近做一个演示的管理系统项目,需要用到分页控件,在网上找了很多,依然找到与UI模版匹配的,最后干脆自己写一个. 分页控件分析: 1.分页控件分简单显示和复杂显示两种: 2.包含上一页.下一页以及页码明 ...
- 【工具】Idea GenerateAllSetter
使用工具自动生成setter方法调用,不是idea原生态生成getter/setter https://github.com/gejun123456/intellij-generateAllSetMe ...
- 使用docker 部署rabbitmq 镜像
1.使用带有web管理功能 sudo docker pull rabbitmq:management 2.运行镜像文件创建容器 sudo docker run -d --name rabbitmq - ...
- Unity 5.x动态加载光照信息(所有坑已踩)
能搜到这的应该是被新的烘焙系统坑了少时间,4.x到5.x美术必须重新烘焙,关于美术的没什么说的,只有---重新烘焙! 新的烘焙系统,为了兼容5.x的多场景编辑功能,将烘焙信息从mesh全部挪到了一个中 ...
- 《ServerSuperIO Designer IDE使用教程》-3.Modbus协议,读取多个寄存器,实现多种数据类型解析。发布:v4.2.2版本
更新内容,v4.2.2版本:1.增加Modbus协议读取多个寄存器,并且按多种数据类型解析数据.2.Modbus Serial和Modbus TCP两个驱动合并成一个驱动.3.修改数据库结构,保存配置 ...