[ASP.NET Core 3框架揭秘] 依賴注入[4]:一個Mini版的依賴注入框架


在前面的章節中,我們從純理論的角度對依賴注入進行了深入論述,我們接下來會對.NET Core依賴注入框架進行單獨介紹。為了讓讀者朋友能夠更好地理解.NET Core依賴注入框架的設計與實現,我們按照類似的原理創建了一個簡易版本的依賴注入框架,也就是我們在前面多次提及的Cat。

源代碼下載

普通服務的注冊與消費
泛型服務的注冊與消費
多服務實例的提供
服務實例的生命周期

一、編程體驗

雖然我們對這個名為Cat的依賴注入框架進行了最大限度的簡化,但是與.NET Core框架內部使用的真實依賴注入框架相比,Cat不僅采用了一致的設計,而且幾乎具備了后者所有的功能特性。為了讓大家對Cat具有一個感官的認識,我們先來演示一下如何利用它來提供我們所需的服務實例。

作為依賴注入容器的Cat對象不僅僅作為服務實例的提供者,它同時還需要維護着服務實例的生命周期。Cat提供了三種生命周期模式,如果要了解它們之間的差異,就必須對多個Cat之間的層次關系有充分的認識。一個代表依賴注入容器的Cat對象用來創建其他的Cat對象,后者視前者為“父容器”,所以多個Cat對象通過其“父子關系”維系一個樹形層次化結構。不過這僅僅是一個邏輯結構而已,實際上每個Cat對象只會按照下圖所示的方式引用整棵樹的根。

3-6_thumb2

在了解了多個Cat對象之間的關系之后,對於三種預定義的生命周期模式就很好理解了。如下所示的Lifetime枚舉代表着三種生命周期模式,其中Transient代表容器針對每次服務請求都會創建一個新的服務實例,而Self則是將提供服務實例保存在當前容器中,它代表針對某個容器范圍內的單例模式,Root則是將每個容器提供的服務實例統一存放到根容器中,所以該模式能夠在多個“同根”容器范圍內確保提供的服務是單例的。

public enum Lifetime
{
    Root,
    Self,
    Transient
}

代表依賴注入容器的Cat對象之所以能夠為我們提供所需服務實例,其根本前提是相應的服務注冊在此之前已經添加到容器之中。服務總是針對服務類型(接口、抽象類或者具體類型)進行注冊,Cat通過定義的擴展方法提供了如下三種注冊方式。除了直接提供服務實例的形式外(默認采用Root模式),我們在注冊服務的時候必須指定一個具體的生命周期模式。

  • 指定具體的實現類型。

  • 提供一個服務實例。

  • 指定一個創建服務實例的工廠。

我們定義了如下的接口和對應的實現類型來演示針對Cat的服務注冊。其中Foo、Bar、Baz和Gux分別實現了對應的接口IFoo、IBar、IBaz和IGux,其中Gux類型上標注了一個MapToAttribute特性注冊了與對應接口IGux之間的映射。為了反映Cat對服務實例生命周期的控制,我們讓它們派生於同一個基類Base。Base實現了IDisposable接口,我們在其構造函數和實現的Dispose方法中輸出相應的文本以確定對應的實例何時被創建和釋放。我們還定義了一個泛型的接口IFoobar<T1, T2>和對應的實現類Foobar<T1, T2>來演示Cat針對泛型服務實例的提供。

public interface IFoo {}
public interface IBar {}
public interface IBaz {} 
public interface IGux {}
public interface IFoobar<T1, T2> {}
public class Base : IDisposable
{
    public Base() => Console.WriteLine($"Instance of {GetType().Name} is created.");
    public void Dispose()  => Console.WriteLine($"Instance of {GetType().Name} is disposed.");
}

public class Foo : Base, IFoo{ }
public class Bar : Base, IBar{ }
public class Baz : Base, IBaz{ } 
[MapTo(typeof(IGux), Lifetime.Root)]
public class Gux : Base, IGux { }
public class Foobar<T1, T2>: IFoobar<T1,T2>
{
    public IFoo Foo { get; }
    public IBar Bar { get; }
    public Foobar(IFoo foo, IBar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}

在如下所示的代碼片段中我們創建了一個Cat對象並采用上面提到的方式針對接口IFoo、IBar和IBaz注冊了對應的服務,它們采用的生命周期模式分別為Transient、Self和Root。然后我們還調用了另一個將當前入口程序集作為參數的Register方法,該方法會解析指定程序集中標注了MapToAttribute特性的類型並作相應的服務注冊,對於我們演示的程序來,該方法會完成針對IGux/Gux類型的服務注冊。接下來我們利用Cat對象創建了它的兩個子容器,並調用子容器的GetService<T>方法提供相應的服務實例。

class Program
{
    static void Main()
    {
        var root = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar>(_=> new Bar(), Lifetime.Self) 
            .Register<IBaz, Baz>(Lifetime.Root) 
            .Register(Assembly.GetEntryAssembly());
        var cat1 = root.CreateChild();
        var cat2 = root.CreateChild();

        void GetServices<TService>(Cat cat)
        {
            cat.GetService<TService>();
            cat.GetService<TService>();
        }

        GetServices<IFoo>(cat1);
        GetServices<IBar>(cat1);
        GetServices<IBaz>(cat1); 
        GetServices<IGux>(cat1);
        Console.WriteLine();
        GetServices<IFoo>(cat2);
        GetServices<IBar>(cat2);
        GetServices<IBaz>(cat2); 
        GetServices<IGux>(cat2);
    }
}

上面的程序運行之后會在控制台上輸出如圖3-7所示的結果,輸出的內容不僅表明Cat能夠根據添加的服務注冊提供對應類型的服務實例,還體現了它對生命周期的控制。由於服務IFoo被注冊為Transient服務,所以Cat針對該接口的服務提供四次請求都會創建一個全新的Foo對象。IBar服務的生命周期模式為Self,如果我們利用同一個Cat對象來提供對應的服務實例,該Cat對象只會創建一個Bar對象,所以整個過程中會創建兩個Bar對象。IBaz和IGux服務采用Root生命周期,所以具有同根的兩個Cat對象提供的總是同一個Baz/Gux對象,后者只會被創建一次。

3-7_thumb2

除了提供類似於IFoo、IBar和IBaz這樣非泛型的服務實例之外,如果具有針對泛型定義(Generic Definition)的服務注冊,Cat同樣也能提供泛型服務實例。如下面的代碼片段所示,在為創建的Cat對象添加了針對IFoo和IBar接口的服務注冊之后,我們調用Register方法注冊了針對泛型定義IFoobar<,>的服務注冊,具體的實現類型為Foobar<,>。當我們利用Cat對象提供一個類型為IFoobar<IFoo, IBar>的服務實例的時候,它會創建並返回一個Foobar<Foo, Bar>對象。

public class Program
{
    public static void Main()
    {
        var cat = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar, Bar>(Lifetime.Transient)
            .Register(typeof(IFoobar<,>), typeof(Foobar<,>), Lifetime.Transient);

        var foobar = (Foobar<IFoo, IBar>)cat.GetService<IFoobar<IFoo, IBar>>();
        Debug.Assert(foobar.Foo is Foo);
        Debug.Assert(foobar.Bar is Bar);
    }
}

當我們在進行服務注冊的時候,可以為同一個類型添加多個服務注冊。雖然添加的所有服務注冊均是有效的,不過由於擴展方法GetService<TService>總是返回一個唯一的服務實例,我們對該方法采用了“后來居上”的策略,即總是采用最近添加的服務注冊來創建服務實例。如果我們調用另一個擴展方法GetServices<TService>,它將利用返回根據所有服務注冊提供的服務實例。

如下面的代碼片段所示,我們為創建的Cat對象添加了三個針對Base類型的服務注冊,對應的實現類型分別為Foo、Bar和Baz。我們最后將Base作為泛型參數調用了GetServices<Base>方法,該方法會返回包含三個Base對象的集合,集合元素的類型分別為Foo、Bar和Baz。

public class Program
{
    public static void Main()
    {
        var services = new Cat()
            .Register<Base, Foo>(Lifetime.Transient)
            .Register<Base, Bar>(Lifetime.Transient)
            .Register<Base, Baz>(Lifetime.Transient)
            .GetServices<Base>();
        Debug.Assert(services.OfType<Foo>().Any());
        Debug.Assert(services.OfType<Bar>().Any());
        Debug.Assert(services.OfType<Baz>().Any());
    }
}

如果提供的服務實例實現了IDisposable接口,我們應該在適當的時候調用其Dispose方法釋放該服務實例。由於服務實例的生命周期完全由作為依賴注入容器的Cat對象來管理,那么通過調用Dispose方法來釋放服務實例自然也應該由它來負責。Cat針對提供服務實例的釋放策略取決於采用的生命周期模式,具體的策略如下:

  • Transient和Self:所有實現了IDisposable接口的服務實例會被當前Cat對象保存起來,當Cat對象自身的Dispose方法被調用的時候,這些服務實例的Dispose方法會隨之被調用。
  • Root:由於服務實例保存在作為根容器的Cat對象上,所以當這個Cat對象的Dispose方法被調用的時候,這些服務實例的Dispose方法會隨之被調用。

上述的釋放策略可以通過如下的演示實例來印證。我們在如下的代碼片段中創建了一個Cat對象,並添加了相應的服務注冊。我們接下來調用了CreateChild方法創建代表子容器的Cat對象,並用它提供了四個注冊服務對應的實例。

class Program
{
    static void Main()
    {
        using (var root = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar>(_ => new Bar(), Lifetime.Self)
            .Register<IBaz, Baz>(Lifetime.Root)
            .Register(Assembly.GetEntryAssembly()))
        {
            using (var cat = root.CreateChild())
            {
                cat.GetService<IFoo>();
                cat.GetService<IBar>();
                cat.GetService<IBaz>();
                cat.GetService<IGux>();
                Console.WriteLine("Child cat is disposed.");
            }
            Console.WriteLine("Root cat is disposed.");
        }
    }
}

由於兩個Cat對象的創建都是在using塊中進行的,所以它們的Dispose方法都會在using塊結束的地方被調用。為了確定方法被調用的時機,我們特意在控制台上打印了相應的文字。該程序運行之后會在控制台上輸出如下圖所示的結果,我們可以看到當作為子容器的Cat對象的Dispose方法被調用的時候,由它提供的兩個生命周期模式分別為Transient和Self的兩個服務實例(Foo和Bar)被正常釋放了。至於生命周期模式為Root的服務實例Baz和Gux,它的Dispose方法會延遲到作為根容器的Cat對象的Dispose方法被調用的時候。

3-8_thumb2

二、設計與實現

在完成針對Cat的編程體驗之后,我們來聊聊這個依賴注入容器的設計原理和具體實現。由於作為依賴注入容器的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> actory { 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為鏈表頭,那么這個方法會返回鏈表上的所有ServiceRegistry對象。下圖體現了服務注冊核心三要素和鏈表結構。

3-9_thumb2

在了解了表示服務注冊的ServiceRegistry之后,我們來着重介紹表示依賴注入容器的Cat類型。如下面的代碼片段所示,Cat同時實現了IServiceProvider和IDisposable接口,定義在前者中的GetService方法用於提供服務實例。作為根容器的Cat對象通過公共構造函數創建,另一個內部構造函數則用來創建作為子容器的Cat對象,指定的Cat對象將作為父容器。

public class Cat : IServiceProvider, IDisposable
{
    internal readonly Cat                             _root;
    internal readonly ConcurrentDictionary<Type, ServiceRegistry>     _registries;
    private readonly ConcurrentDictionary<Key, object>             _services;
    private readonly ConcurrentBag<IDisposable>                 _disposables;
    private volatile bool _disposed;

    public Cat()
    {
        _registries = new ConcurrentDictionary<Type, ServiceRegistry>();
        _root = this;
        _services = new ConcurrentDictionary<Key, object>();
        _disposables = new ConcurrentBag<IDisposable>();
    }

    internal Cat(Cat parent)
    {
        _root = parent._root;
        _registries = _root._registries;
        _services = new ConcurrentDictionary<Key, object>();
        _disposables = new ConcurrentBag<IDisposable>();
    }

    private void EnsureNotDisposed()
    {
        if (_disposed)
        {
            throw new ObjectDisposedException("Cat");
        }
    }
    ...
}

作為根容器的Cat對象通過_root字段表示。_registries字段返回的ConcurrentDictionary<Type, ServiceRegistry>對象用來存儲所有添加的服務注冊,該字典對象的Key和Value分別表示服務類型和ServiceRegistry鏈表,下圖體現這一映射關系。由於需要負責完成對提供服務實例的釋放工作,所以我們需要將實現了IDisposable接口的服務實例保存在通過_disposables字段表示的集合中。

3-10_thumb2

由當前Cat對象提供的非Transient服務實例保存在由_services字段表示的一個ConcurrentDictionary<Key, object>對象上,該字典對象的鍵類型為如下所示的Key,它相當於是創建服務實例所使用的ServiceRegistry對象和泛型參數類型數組的組合。

internal class Key : IEquatable<Key>
{
    public ServiceRegistry     Registry { get; }
    public Type[]         GenericArguments { get; }

    public Key(ServiceRegistry registry, Type[] genericArguments)
    {
        Registry  = registry;
        GenericArguments = genericArguments;
    }

    public bool Equals(Key other)
    {
        if (Registry != other.Registry)
        {
            return false;
        }
        if (GenericArguments.Length != other.GenericArguments.Length)
        {
            return false;
        }
        for (int index = 0; index < GenericArguments.Length; index++)
        {
            if (GenericArguments[index] != other.GenericArguments[index])
            {
                return false;
            }
        }
        return true;
    }

    public override int GetHashCode()
    {
        var hashCode = Registry.GetHashCode();
        for (int index = 0; index < GenericArguments.Length; index++)
        {
            hashCode ^= GenericArguments[index].GetHashCode();
        }
        return hashCode;
    }
    public override bool Equals(object obj) => obj is Key key ? Equals(key) : false;
}

雖然我們為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 key = new Key(registry, genericArguments);
        var serviceType = registry.ServiceType;

        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);
                    if (service is IDisposable disposable && disposable != this)
                    {
                        _disposables.Add(disposable);
                    }
                    return service;
                }
        }

        object GetOrCreate(ConcurrentDictionary<Key, object> services,  ConcurrentBag<IDisposable> disposables)
        {
            if (services.TryGetValue(key, out var service))
            {
                return service;
            }
            service = registry.Factory(this, genericArguments);
            services[key] = service;
            if (service is IDisposable 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;
        //IEnumerable<T>
        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, Type.EmptyTypes)).ToArray();
            Array array = Array.CreateInstance(elementType, services.Length);
            services.CopyTo(array, 0);
            return array;
        }

        //Generic
        if (serviceType.IsGenericType && !_registries.ContainsKey(serviceType))
        {
            var definition = serviceType.GetGenericTypeDefinition();
            return _registries.TryGetValue(definition, out registry)
                ? GetServiceCore(registry, serviceType.GetGenericArguments())
                : null;
        }

        //Normal
        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();
        }
        _disposables.Clear();
        _services.Clear();
    }
    ...
}

三、擴展方法

為了方便注冊服務,我們定義了如下六個Register擴展方法。由於服務注冊的添加總是需要調用Cat自身的Register方法來完成,所以這些方法最終都需要創建一個代表服務注冊的ServiceRegistry對象。對於一個ServiceRegistry對象來說,它最為核心的元素莫過於表示服務實例創建工廠的Func<Cat,Type[], object>對象,所以上述這六個擴展方法需要解決的就是創建這么一個委托對象。

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(this Cat cat, Type serviceType, object instance)
    {
        Func<Cat, Type[], object> factory = (_, arguments) => instance;
        cat.Register(new ServiceRegistry(serviceType, Lifetime.Root, factory));
        return cat;
    }

    public static Cat Register<TService>(this Cat cat, TService instance)
    {
        Func<Cat, Type[], object> factory = (_, arguments) => instance;
        cat.Register(new ServiceRegistry(typeof(TService),  Lifetime.Root,  factory));
        return cat;
    }

    public static Cat Register(this Cat cat, Type serviceType, 
    Func<Cat, object> factory, Lifetime lifetime)
    {
        cat.Register(new ServiceRegistry(serviceType, lifetime,  (_, arguments) => factory(_)));
        return cat;
    }

    public static Cat Register<TService>(this Cat cat, 
    Func<Cat,TService> factory, Lifetime lifetime)
    {
        cat.Register(new ServiceRegistry(typeof(TService), lifetime,  (_,arguments)=>factory(_)));
        return cat;
    }

    private static object Create(Cat cat, Type type, Type[] genericArguments)
    {
        if (genericArguments.Length > 0)
        {
            type = type.MakeGenericType(genericArguments);
        }
        var constructors = type.GetConstructors();
        if (constructors.Length == 0)
        {
            throw new InvalidOperationException($"Cannot create the instance of 
                {type} which does not have a public constructor.");
        }
        var constructor = constructors.FirstOrDefault(it =>   it.GetCustomAttributes(false).OfType<InjectionAttribute>().Any());
        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++)
        {
            arguments[index] = cat.GetService(parameters[index].ParameterType);
        }
        return constructor.Invoke(arguments);
    }
}

由於前兩個重載指定的是服務實現類型,所以我們需要調用對應的構造函數來創建服務實例,這一邏輯實現在私有的Create方法中。第三個擴展方法指定的直接就是服務實例,所以我們很容易將提供的參數轉換成一個Func<Cat,Type[], object>。

我們刻意簡化了構造函數的篩選邏輯。為了解決構造函數的選擇問題,我們引入如下這個InjectionAttribute特性。我們將所有公共實例構造函數作為候選的構造函數,並會優先選擇標注了該特性的構造函數。當構造函數被選擇出來后,我們需要通過分析其參數類型並利用Cat對象來提供具體的參數值,這實際上是一個遞歸的過程。最終我們將針對構造函數的調用轉換成Func<Cat,Type[], object>對象,進而創建出表示服務注冊的ServiceRegistry對象。

[AttributeUsage( AttributeTargets.Constructor)]
public class InjectionAttribute: Attribute {}

前面給出的代碼片段還提供了HasRegistry和HasRegistry<T>方法來確定指定類型的服務注冊是否存在。除此之外,用於提供服務實例的泛型方法GetService<T>和用於提供所有指定類型服務實例的GetServices<TService>方法采用了如下的定義方式。

public static class CatExtensions
{
    public static IEnumerable<TService> GetServices<T>(this Cat cat)  => cat.GetService<IEnumerable<TService >>();
    public static TService GetService<TService >(this Cat cat)  => (TService)cat.GetService(typeof(T));
}

上述六個擴展方法幫助我們完成針對單一服務的注冊,有時間我們的項目中可能會出現非常多的服務需要注冊,如何能夠完成針對它們的批量注冊會是不錯的選擇。我們的依賴注入框架提供了針對程序集范圍的批量服務注冊。為了標識帶注冊的服務,我們需要在服務實現類型上標注如下這個MapToAttribute類型,並指定服務類型(一般為它實現的接口或者繼承的基類)和生命周期。

[AttributeUsage( AttributeTargets.Class, AllowMultiple = true)]
public sealed class MapToAttribute: Attribute
{       
    public Type     ServiceType { get; }
    public Lifetime     Lifetime { get; }

    public MapToAttribute(Type serviceType, Lifetime lifetime)
    {
        ServiceType = serviceType;
        Lifetime = lifetime;
    }
}

針對程序集范圍的批量服務注冊實現在Cat的如下這個Register擴展方法中。如下面的代碼片段所示,該方法會從指定程序集中獲取所有標注了MapToAttribute特性的類型,並提取出服務類型、實現類型和生命周期模型,然后利用它們批量完成所需的服務注冊。

public static class CatExtensions
{   
    public static Cat Register(this Cat cat, Assembly assembly)
    {
        var typedAttributes = from type in assembly.GetExportedTypes()
            let attribute = type.GetCustomAttribute<MapToAttribute>()
            where attribute != null
            select new { ServiceType = type, Attribute = attribute };
        foreach (var typedAttribute in typedAttributes)
        {
            cat.Register(typedAttribute.Attribute.ServiceType, 
                typedAttribute.ServiceType, typedAttribute.Attribute.Lifetime);
        }
        return cat;
    }
}

除了上述這七個用來注冊服務的Register擴展方法,我們還為Cat類型定義了如下兩個擴展方法,其中CreateService<T>方法以泛型參數的形式指定獲取服務實例對應注冊的類型,CreateServices<T>方法會提供指定服務類型的所有實例,而CreateChild方法則幫助我們創建一個代表子容器的Cat對象。

public static class CatExtensions
{   
    public static T GetService<T>(this Cat cat) => (T)cat.GetService(typeof(T));
    public static IEnumerable<T> GetServices<T>(this Cat cat) => cat.GetService<IEnumerable<T>>();
    public static Cat CreateChild(this Cat cat) => new Cat(cat);
}

[ASP.NET Core 3框架揭秘] 依賴注入[1]:控制反轉
[ASP.NET Core 3框架揭秘] 依賴注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依賴注入[3]:依賴注入模式
[ASP.NET Core 3框架揭秘] 依賴注入[4]:一個迷你版DI框架
[ASP.NET Core 3框架揭秘] 依賴注入[5]:利用容器提供服務
[ASP.NET Core 3框架揭秘] 依賴注入[6]:服務注冊
[ASP.NET Core 3框架揭秘] 依賴注入[7]:服務消費
[ASP.NET Core 3框架揭秘] 依賴注入[8]:服務實例的生命周期
[ASP.NET Core 3框架揭秘] 依賴注入[9]:實現概述
[ASP.NET Core 3框架揭秘] 依賴注入[10]:與第三方依賴注入框架的適配


免責聲明!

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



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