依赖注入[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字段表示的待释放服务实例列表中。对于RootSelf生命周期模式,该方法会先根据提供的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 {}

上面给出的代码片段还提供了两个HasRegistryHasRegistry<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框架[服务消费]

原文地址:https://www.cnblogs.com/artech/p/net-core-di-05.html

时间: 2024-10-07 11:42:46

依赖注入[5]: 创建一个简易版的DI框架[下篇]的相关文章

依赖注入[4]: 创建一个简易版的DI框架[上篇]

本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度对依赖注入进行了深入论述,为了让读者朋友能够更好地理解.NET Core的依赖注入框架的设计思想和实现原理,我们创建了一个简易版本的DI框架,也就是我们在前面文章中多次提及的Cat.我们会上下两篇来介绍这个被称为为Cat的DI框架,上篇介绍编程模型,下篇关注设计实现.[源代码从这里下载] 目录一.DI容器的层

使用Python创建一个简易的Web Server

Python 2.x中自带了SimpleHTTPServer模块,到Python3.x中,该模块被合并到了http.server模块中.使用该模块,可以快速创建一个简易的Web服务器. 我们在C:\Users\%USERNAME%\用户目录下,创建一个html目录,将html/jpg等网页文件拷贝到该目录下,启动一个cmd命令行窗口,进入html目录,执行如下命令即可创建一个简易的Web Server: python -m http.server 8888 图01-使用python创建一个简单的

从零开始实现一个简易的Java MVC框架(三)--实现IOC

Spring中的IOC IoC全称是Inversion of Control,就是控制反转,他其实不是spring独有的特性或者说也不是java的特性,他是一种设计思想.而DI(Dependency Injection),即依赖注入就是Ioc的一种实现方式.关于Ioc和DI的具体定义和优缺点等大家可以自行查找资料了解一下,这里就不详细赘述,总之spring的IoC功能很大程度上便捷了我们的开发工作. 在实现我们的Ioc之前,我们先了解一下spring的依赖注入,在spring中依赖注入有三种方式

Spring 3.0 学习-DI 依赖注入_创建Spring 配置-使用一个或多个XML 文件作为配置文件,使用自动注入(byName),在代码中使用注解代替自动注入,使用自动扫描代替xml中bea

文章大纲 在xml中声明bean和注入bean 在xml中声明bean和自动注入bean 自动扫描bean和自动注入bean 对自动扫描bean增加约束条件 首次接触spring请参考 Spring 3.0 学习-环境搭建和三种形式访问 1.典型的Spring XML 配置文件表头 <?xml version="1.0" encoding="UTF-8"?><!-- 一般化的Spring XML 配置 --> <beans xmlns=

基于OpenGL编写一个简易的2D渲染框架01——创建窗口

最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的是 vs2013,使用C++语言编写项目.这个小项目叫Simple2D,意味着简易的2D框架.最终的目的是可以渲染几何图形和图片,最后尝试加上一个2D粒子系统和Box2D物理引擎,并编译一个简单的游戏. 第一步,就是创建一个Win32项目. 接下来,生成一个窗口.编写一个RenderWindow类,

实现一个简易版webpack

现实 webpack 的打包产物 大概长这样(只把核心代码留下来): 实现一个简版的webpack 依葫芦画瓢,实现思路分2步: 1. 分析入口文件,把所有的依赖找出来(包括所有后代的依赖) 2. 拼接出类似上面的立即执行函数 找依赖 const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/tr

一个简易版的Function.prototype.bind实现

重新看<JavaScript设计模式与开发实践>一书,第32页发现个简易版的Function.prototype.bind实现,非常容易理解,记录在这了. Function.prototype.bind = function (context) { var self = this; return function () { return self.apply(context, arguments); }; }; var obj = { name: 'sven' }; var func = fu

基于OpenGL编写一个简易的2D渲染框架-05 渲染文本

阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤: 获取要绘制的字符的 Unicode 码,使用 FreeType 库获取对应的位图数据,添加到字符表中(后面同样的字符可以再表中直接索引),将字符表上的字符填充到一张纹理上.计算每个字符的纹理坐标,使用渲染器绘制 注意的问题: 对于中英文混合的字符串,使用 char 存储时,英文字符占 1 个字节,而中

基于OpenGL编写一个简易的2D渲染框架-07 鼠标事件和键盘事件

这次为程序添加鼠标事件和键盘事件 当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理.为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象. 实现思路: 要实现以上的功能,需要几个对象: 事件分派器:EventDispatcher,负责将 BaseEvent 分派给 EventListener 对象 事件监听器:EventListener,这只是一个接口类,接受 BaseEvent 的对象,真正的处理在它的子类中实现 事件:BaseEvent,储存