释义

在实际开发中,我们往往将业务、功能等抽象成一个个对象,我们把这些对象称之为服务。有的服务是比较复杂的,比如A服务的构造函数中包含B服务,那么就说A服务依赖B服务,而B服务又依赖C服务,甚至更长的依赖链接,这样依赖我们想实例化一个A服务流程是比较繁杂的,如果要实例化多个,那简直就是灾难。因此,依赖注入容器诞生了。谈到依赖注入,我们往往把它和IOC(控制反转)混为一谈,但实际上,两者并不是同一个概念,IOC强调的是服务的消费交由框架来处理,我们只需要注册服务,无需操心服务何时创建、消费、销毁,这是一种设计思想。而依赖注入则是工厂设计模式的一种体现,我们把用到的服务注册到依赖注入容器中,服务实例的创建交由依赖注入容器来提供,依赖注入容器的使用则是交由应用程序的(程序员控制),因此IOC是建立在依赖注入容器之上的,两者不能混为一谈。

依赖

要使用官方的依赖注入框架,我们需要依赖以下两个包

image-20230612113522437

两个分别为依赖注入框架的抽象与实现。

定义服务

为了演示依赖注入容器的使用,定义以下服务抽象及其实现

public interface IBar{}

public interface IBaz{}

public interface IFoo{}

public interface IFooBar{}

public interface IQux{}

public class Bar:IBar{}

public class Baz:IBaz{}

public class Foo:IFoo{}

public class Foobar<T1, T2> : IFoobar<T1, T2>
{
    public T1 Foo { get; set; }
    public T2 Bar { get; set; }

    public Foobar(T1 foo, T2 bar)
    {
        Foo = foo;
        Bar = bar;
    }
}


public class Qux:IQux
{
    public Qux(IFoo foo,IBar bar)
    {
        
    }

    public Qux(IBar bar,IBaz baz)
    {
        
    }

    public Qux()
    {
        
    }
}

服务的注册与获取

依赖注入容器体现为一个ServiceProvider对象,一个服务的注册被抽象为一个ServiceDescriptor对象,而一个依赖注入容器实际上是一组服务注册对象的集合

image-20230612114613546

因此我们可以使用Add方法向ServiceCollection容器中注册服务,但一个ServiceDescriptor是比较复杂的,因此官方提供了一系列的扩展方法简化服务的注册过程,演示如下

ServiceProvider provider = new ServiceCollection()
    .AddScoped<IFoo, Foo>() //注册IFoo服务,其实现为Foo
    .AddScoped<IBar, Bar>() //同上
    .BuildServiceProvider();

IFoo foo = provider.GetService<IFoo>()!; //获取IFoo服务
IBar bar = provider.GetService<IBar>()!; //获取IBar服务

从上面的代码片段可以看出,ServiceCollection容器首先注册要使用的服务,最后使用BuildServiceProvider()方法返回一个ServiceProvider对象,它包含了所有服务的注册信息,服务的创建由它的扩展方法提供。

服务的生命周期

服务在注册时需要声明生命周期,官方提供了三种服务生命周期,分别为SingletonScopeTransient,三种生命周期特点如下:

  • Singleton服务为单例服务,该服务只会创建一个服务实例并保存在根容器中,后续容器再次创建该服务实例,只会获得该实例的引用,其生命周期与跟容器生命周期相同
  • Scope服务笔者称之为作用域服务,该服务的生命周期由创建它实例的容器决定,且相对于创建它实例的容器来说,他和Singleton具备一样的特点
  • Transient服务,每次创建都会生成一个新的服务实例,生命更周期由创建它的容器决定

下面来看一段代码片段

var provider = new ServiceCollection()
    .AddSingleton<IFoo, Foo>()
    .AddScoped<IBar, Bar>()
    .AddTransient<IBaz,Baz>()
    .BuildServiceProvider();

var scope = provider.CreateScope();//创建一个子容器
IFoo foo = provider.GetRequiredService<IFoo>();
IFoo foo1 = scope.ServiceProvider.GetRequiredService<IFoo>();

Console.WriteLine("Singleton生命周期:");//验证Singleton服务的特点
if (foo == foo1) //不同容器创建的实例是否为同一对象
    Console.WriteLine("foo==foo1");
else
    Console.WriteLine("foo!=foo1");

Console.WriteLine("Scope生命周期:");//验证Scope服务的特点
IBar bar = provider.GetRequiredService<IBar>();
IBar bar1 = provider.GetRequiredService<IBar>();
IBar bar2 = scope.ServiceProvider.GetRequiredService<IBar>();
IBar bar3 = scope.ServiceProvider.GetRequiredService<IBar>();
if (bar == bar1)//相同容器创建的服务实例是否为同一对象
    Console.WriteLine("bar==bar1");
else
    Console.WriteLine("bar!=bar1");

if (bar == bar2)//不同容器创建的服务实例是否为同一对象
    Console.WriteLine("bar==bar2");
else
    Console.WriteLine("bar!=bar2");

if (bar2 == bar3)
    Console.WriteLine("bar2==bar3");
else
    Console.WriteLine("bar2!=bar3");

Console.WriteLine("Transient生命周期:");//验证Transient服务的特点
IBaz baz = provider.GetRequiredService<IBaz>();
IBaz baz1 = provider.GetRequiredService<IBaz>();
IBaz baz2 = scope.ServiceProvider.GetRequiredService<IBaz>();
IBaz baz3 = scope.ServiceProvider.GetRequiredService<IBaz>();
if (baz == baz1)//相同容器创建的服务实例是否为同一对象
    Console.WriteLine("baz==baz1");
else
    Console.WriteLine("baz!=baz1");
    
if (baz == baz2)//不同容器创建的服务实例是否为同一对象
    Console.WriteLine("baz==baz2");
else
    Console.WriteLine("baz!=baz2");
    
if (baz2 == baz3)
    Console.WriteLine("baz2==baz3");
else
    Console.WriteLine("baz2!=baz3");

这段代码的执行结果如下

image-20230612144659184

服务有效性验证

服务虽然能注册成功,但依赖注入容器在创建服务实例时存在失败的可能的,因此对服务注册的有效性进行验证是有必要的。服务的有效性验证体现为一ServiceProviderOptions对象

ServiceProviderOptions options = new ServiceProviderOptions();//服务有效性验证选项
options.ValidateScopes = true;//是否验证服务范围,服务范围验证的是A服务依赖的其它服务与A服务的生命周其是否一直
options.ValidateOnBuild = true;//是否在构建ServiceProvider时验证服务有效性,验证服务的构造函数能否被调用,例如服务的构造函数有且仅有私有构造函数,那么该服务的注册就是无效的
  • ValidateScopes验证的是服务的依赖链生命周期是否一致,如果不一致可能会导致严重的内存泄露风险,如一个生命周其为Singleton的A服务依赖了一个生命周期为Scope的B服务,那么B服务的生命周期将由A服务决定而不是创建它的容器决定。该选项默认为false,不做验证。
  • ValidateOnBuild验证的是服务的构造函数是否可用

服务创建策略

官方依赖注入容器在创建服务实例时遵循两个原则

  • 后来居上原则,针对同一服务如果注册了多个实现,那么在获取单个实例时总是创建后注册的服务实例
  • 参数最多原则,在创建服务实例时总是会选择构造函数参数最多且参数均已注册的构造函数来实例化服务
var provider = new ServiceCollection()
    .AddSingleton<IQux,Qux>()
    .AddSingleton<IFoo,Foo>()
    .AddSingleton<IBar,Bar>()
    .AddSingleton<IBaz,Baz>()
    .BuildServiceProvider();
try
{
    var qux = provider.GetService<IQux>();//此处会抛出异常,因为Qux有两个多参构造函数,参数数量均为2,且构造函数依赖的服务都被注册,无法确定使用那个函数
}
catch (Exception e)
{
    Console.WriteLine(e);
}

动态创建服务

有些服务是程序运行时动态创建的,典型案例便是MVC开发中的Controller,如果你熟悉MVC的开发模式,你会发现所有Controller都为被注册,针对这种未注册的服务官方提供了ActivatorUtilities工具类

var provider = new ServiceCollection()
    .AddScoped<IFoo, Foo>()
    .AddScoped<IBar, Bar>()
    .BuildServiceProvider();
var foobar = ActivatorUtilities.CreateInstance<Foobar<IFoo, IBar>>(provider);//foobar的构造函数依赖的服务都被注册,但是Foobar没有被注册
Console.WriteLine(foobar?.GetType().Name);