依賴注入[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框架[服務消費]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM