依赖注入[7]: .NET Core DI框架[服务注册]

包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象。服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到指定IServiceCollection集合对象中的过程。

目录 一、ServiceDescriptor 二、IServiceCollection      Add      Add{Lifetime}      TryAdd      TryAdd{Lifetime}      TryAddEnumerable      RemoveAll & Replace

一、ServiceDescriptor

通过《依赖注入[6]: .NET Core DI编程体验》的实例演示我们知道作为DI容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创建的,IServiceCollection对象是一个存放服务注册信息的集合。Cat中的服务注册是通过一个类型为ServiceRegistry的对象表示的,在IServiceCollection/IServiceProvider为核心的DI框架中,与之对应的类型为ServiceDescriptor。

DI框架将服务注册存储在一个通过IServiceCollection接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下我们使用的是实现该接口的ServiceCollection类型。

public class ServiceDescriptor
{
    public Type                 ServiceType { get; }
    public ServiceLifetime             Lifetime { get; }

    public Type                 ImplementationType { get; }
    public Func<IServiceProvider, object>     ImplementationFactory { get; }
    public object                 ImplementationInstance { get; }
    
    public ServiceDescriptor(Type serviceType, object instance);
    public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
    public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}

ServiceDescriptor的其他三个属性体现了服务实例的三种提供方式,并对应着三个构造函数。如果我们指定了服务的实现类型(对应于ImplementationType属性),那么最终的服务实例将通过调用定义在实现类型中某一个构造函数来创建。如果指定的是一个Func<IServiceProvider, object>对象(对应于ImplementationFactory属性),那么IServiceProvider对象将会将自身作为输入参数调用该委托对象来提供服务实例。如果我们直接指定一个现有的对象(对应的属性为ImplementationInstance),那么该对象就是最终提供的服务实例。

如果我们采用直接提供服务实例的形式来创建ServiceDescriptor对象,意味着服务注册默认采用Singleton生命周期模式。对于通过其他两个构造函数创建创建的ServiceDescriptor对象来说,则需要显式指定采用的生命周期模式。相较于ServiceDescriptor,我们在Cat框架中定义的ServiceRegistry显得更加精炼,因为我们直接提供了一个类型为Func<Cat,Type[], object>的属性来提供对应的服务实例。

除了调用上面介绍的三个构造函数来创建对应的ServiceDescriptor对象之外,我们还可以提供定义在ServiceDescriptor类型中一系列静态方法来创建该对象。如下面的代码片段所示,ServiceDescriptor提供了如下两个名为Describe的方法重载来创建对应的ServiceDescriptor对象。

public class ServiceDescriptor
{   
    public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime);
    public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}

当我们调用上面两个Describe方法来创建ServiceDescriptor对象的时候总是需要指定采用的生命周期模式,为了让对象创建变得更加简单,ServiceDescriptor中还定义了一系列针对三种生命周期模式的静态工厂方法。如下所示的是针对Singleton模式的一组Singleton方法重载的定义,针对其他两种模式的Scoped和Transient方法具有类似的定义。

public class ServiceDescriptor
{
    public static ServiceDescriptor Singleton<TService, TImplementation>() where TService: class where TImplementation: class, TService;
    public static ServiceDescriptor Singleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService;
    public static ServiceDescriptor Singleton<TService>(Func<IServiceProvider, TService> implementationFactory) where TService: class;
    public static ServiceDescriptor Singleton<TService>(TService implementationInstance) where TService: class;
    public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
    public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance);
    public static ServiceDescriptor Singleton(Type service, Type implementationType);
}

二、IServiceCollection

DI框架将服务注册存储在一个通过IServiceCollection接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下我们使用的是实现该接口的ServiceCollection类型。

public interface IServiceCollection : IList<ServiceDescriptor>
{}
public class ServiceCollection : IServiceCollection
{}

Add

我们在应用启动的时候所做的服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到指定IServiceCollection集合对象中的过程。考虑到服务注册是一个高频调用的操作,所以DI框架为IServiceCollection接口定义了一系列扩展方法完成服务注册的工作,比如下面的这两个Add方法可以将指定的一个或者多个ServiceDescriptor对象添加到IServiceCollection集合中。

public static class ServiceCollectionDescriptorExtensions
{
    public static IServiceCollection Add(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static IServiceCollection Add(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}

Add{Lifetime}

DI框架还针对具体生命周期模式为IServiceCollection接口定义了一系列的扩展方法,它们会根据提供的输入创建出对应的ServiceDescriptor对象并将其添加到指定的IServiceCollection对象中。如下所示的是针对Singleton模式的AddSingleton方法重载的定义,针对其他两个生命周期模式的AddScoped和AddTransient方法具有类似的定义。

public static class ServiceCollectionServiceExtensions
{   
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
    public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services) where TService: class where TImplementation: class, TService;
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, TService implementationInstance)  where TService: class;
    public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)  where TService: class where TImplementation: class, TService;
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)  where TService: class;
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, object implementationInstance);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType);
}

TryAdd

虽然针对同一个服务类型可以添加多个ServiceDescriptor,但这情况只有在应用需要使用到同一类型的多个服务实例的情况下才有意义,比如我们可以注册多个ServiceDescriptor来提供同一个主题的多个订阅者。如果我们总是根据指定的服务类型来提取单一的服务实例,这种情况下一个服务类型只需要一个ServiceDescriptor对象就够了。对于这种场景我们可能会使用如下两个名为TryAdd的扩展方法,该方法会根据指定ServiceDescriptor提供的服务类型判断对应的服务注册是否存在,只有不存在指定类型的服务注册情况下,我们提供的ServiceDescriptor才会被添加到指定的IServiceCollection对象中。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAdd(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static void TryAdd(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}

TryAdd{Lifetime}

扩展方法TryAdd同样具有基于三种生命周期模式的版本,如下所示的针对Singleton模式的TryAddSingleton方法的定义。在指定服务类型对应的ServiceDescriptor不存在的情况下,它们会采用提供的实现类型、服务实例创建工厂以及服务实例来创建生命周期模式为Singleton的ServiceDescriptor对象并将其添加到指定的IServiceCollection对象中。针对其他两种生命周期模式的TryAddScoped和TryAddTransient方法具有类似的定义。

public static class ServiceCollectionDescriptorExtensions
{    
    public static void TryAddSingleton<TService>(this IServiceCollection collection)  where TService: class;
    public static void TryAddSingleton<TService, TImplementation>(this IServiceCollection collection)  where TService: class  where TImplementation: class, TService;
    public static void TryAddSingleton(this IServiceCollection collection,  Type service);
    public static void TryAddSingleton<TService>(this IServiceCollection collection,  TService instance) where TService: class;
    public static void TryAddSingleton<TService>(this IServiceCollection services,  Func<IServiceProvider, TService> implementationFactory)  where TService: class;
    public static void TryAddSingleton(this IServiceCollection collection,  Type service, Func<IServiceProvider, object> implementationFactory);
    public static void TryAddSingleton(this IServiceCollection collection,  Type service, Type implementationType);
}

TryAddEnumerable

除了上面介绍的扩展方法TryAdd和TryAdd{Lifetime}之外,IServiceCollection接口还具有如下两个名为TryAddEnumerable的扩展方法。当TryAddEnumerable方法在决定将指定的ServiceDescriptor添加到IServiceCollection对象之前,它也会做存在性检验。与TryAdd和TryAdd{Lifetime}方法不同的是,该方法在判断执行的ServiceDescriptor是否存在是会同时考虑服务类型和实现类型。

public static class ServiceCollectionDescriptorExtensions
{   
    public static void TryAddEnumerable(this IServiceCollection services, ServiceDescriptor descriptor);
    public static void TryAddEnumerable(this IServiceCollection services, IEnumerable<ServiceDescriptor> descriptors);
}

被TryAddEnumerable方法用来判断存在性的实现类型不只是ServiceDescriptor的ImplementationType属性。如果ServiceDescriptor是通过一个指定的服务实例创建的,那么该实例的类型会作为用来判断存在与否的实现类型。如果ServiceDescriptor是通过提供的服务实例工厂来创建的,那么代表服务实例创建工厂的Func<in T, out TResult>对象的第二个参数类型将被用于判断ServiceDescriptor的存在性。扩张方法TryAddEnumerable的实现逻辑可言通过如下这段程序来验证。

var services = new ServiceCollection();

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == 1);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == 1);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Foo()));
Debug.Assert(services.Count == 1);
Func<IServiceProvider, Foo> factory4Foo = _ => new Foo();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Foo));
Debug.Assert(services.Count == 1);

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Bar>());
Debug.Assert(services.Count == 2);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Baz()));
Debug.Assert(services.Count == 3);
Func<IServiceProvider, Gux> factory4Gux = _ => new Gux();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Gux));
Debug.Assert(services.Count == 4);

如果通过上述策略得到的实现类型为Object,那么TryAddEnumerable会因为实现类型不明确而抛出一个ArgumentException类型的异常。这种情况主要发生在提供的ServiceDescriptor对象是由服务实例工厂创建的情况,所以上面实例中用来创建ServiceDescriptor的工厂类型分别为Func<IServiceProvider, Foo>和Func<IServiceProvider, Gux>,而不是Func<IServiceProvider, object>。

var service = ServiceDescriptor.Singleton<IFoobarbazgux>(_ => new Foo());
new ServiceCollection().TryAddEnumerable(service);

假设我们采用如上所示的方式利用一个Lamda表达式来创建一个ServiceDescriptor对象,对于创建的ServiceDescriptor来说,其服务实例工厂是一个Func<IServiceProvider, object>对象,所以当我们将它作为参数调用TryAddEnumerable方法的会抛出如图1所示的ArgumentException异常,并提示“Implementation type cannot be 'App.IFoobarbazgux' because it is indistinguishable from other services registered for 'App.IFoobarbazgux'.”

图1实现类型不明确导致的异常

RemoveAll & Replace

上面介绍的这些方法最终的目的都是添加新的ServiceDescriptor到指定的IServiceCollection对象中,有的时候我们还希望删除或者替换现有的某个ServiceDescriptor,这种情况下通常发生在需要对当前使用框架中由某个服务提供的功能进行定制的时候。由于IServiceCollection实现了IList<ServiceDescriptor>接口,所以我们可以调用其Clear、Remove和RemoveAt方法来清除或者删除现有的ServiceDescriptor。除此之外,我们还可以选择如下这些扩展方法。

public static class ServiceCollectionDescriptorExtensions
{
    public static IServiceCollection RemoveAll<T>( this IServiceCollection collection);
    public static IServiceCollection RemoveAll(this IServiceCollection collection,  Type serviceType);
    public static IServiceCollection Replace(this IServiceCollection collection,  ServiceDescriptor descriptor);
}

RemoveAll和RemoveAll<T>方法帮助我们针对指定的服务类型来删除添加的ServiceDescriptor。Replace方法会使用指定的ServiceDescriptor去替换第一个具有相同服务类型(对应ServiceType属性)的ServiceDescriptor,实际操作是先删除后添加。如果从目前的IServiceCollection中找不到服务类型匹配的ServiceDescriptor,指定的ServiceDescriptor会直接添加到IServiceCollection对象中,这一逻辑也可以利用如下的程序来验证。

var services = new ServiceCollection();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Foo)));

services.AddSingleton<IFoobarbazgux, Bar>();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Baz>());
Debug.Assert(!services.Any(it=>it.ImplementationType == typeof(Foo)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Bar)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Baz)));

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

[WCF-Discovery] 客户端如何能够“探测”到可用的服务?

当应用了ServiceDiscoveryBehavior行为的服务通过标准终结点DiscoveryEndpoint被发布出来之后(《[WCF-Discovery...

2059
来自专栏desperate633

Java线程通信(Thread Signaling)利用共享对象实现通信忙等(busy waiting)wait(), notify() and notifyAll()信号丢失(Missed Sign

线程通信的目的就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。举个例子,一个线程B可能正在等待来自线程A...

902
来自专栏纯洁的微笑

Java8内存模型—永久代(PermGen)和元空间(Metaspace)

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

1712
来自专栏陈树义

Java并发编程:线程控制

在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期。这篇文章将深入讲解Java如何对线程进行状态控制,比如...

4049
来自专栏技巅

Thrift之代码生成器Compiler原理及源码详细解析1

1475
来自专栏闻道于事

Java多线程详解

每个运行的程序就是一个进程,当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个进程。

1113
来自专栏与神兽党一起成长

使用commons-pool管理FTP连接

在封装一个FTP工具类文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。

1192
来自专栏黑泽君的专栏

java基础学习_多线程01_多线程_day23总结

672
来自专栏马洪彪

Java设计模式(二)抽象工厂模式

一、场景描述 接《Java设计模式(一)工厂模式》 工厂模式有一缺点,就是破坏了类的封闭性原则。例如,如果需要增加Word文件的数据采集,此时按以下步骤操作: ...

40210
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

1852

扫码关注云+社区