作为一名 ASP.NET Core 的开发者,依赖注入可以说是居家旅行开发调试的必备技能。
在这篇文章里,希望通过一些常识性测试题,来巩固学习一下依赖注入的基础知识。
作用域
请问下面这段代码的执行结果是什么?
public interface IServiceA { }
class ServiceA : IServiceA
{
ServiceA()
{
Console.WriteLine("New SA");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IServiceA, ServiceA>();
...
}
}
结果是报错:
System.AggregateException: 'Some services are not able to be constructed'
A suitable constructor for type 'AspNetCore.Services.ServiceA' could not be located.
Ensure the type is concrete and services are registered for all parameters of a public constructor.
官方文档在 Constructor injection behavior 有提过,如果通过构造函数注入,构造函数必须是 public
级别。
为什么 constructor
要 public
呢?因为默认的访问级别是 private
。依赖注入是由 ASP.NET Core
实现的,自然是无法访问 private
级别的构造方法的。
那 class
需不需要是 public
呢?不需要,因为通过方法调用的方式已经让 DI
获取到了 class
,如果是 using namespace
的情况下访问 class
,才需要 class
也是 public
。
生命周期
下面这段代码中,singleton
的 IServiceA
被 HelloController
所依赖,在项目启动之后,没有访问网页的情况下,ServiceA
会被初始化吗?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetType()}");
}
}
ServiceA
并不会被初始化。DI 虽然会检查是否存在 public constructor
,但是不会立即初始化服务实例,只有在服务被使用的时候才会根据注册时的生命周期做初始化。ServiceA
只被 controller
依赖,而 controller
只有在请求过来的时候才会被初始化:
所以 ServiceA
也只有在请求到达 controller
的时候才会跟着 controller
一起被初始化。
如果连续访问三次 controller
,会看到 singleton
在第一次请求到达时被初始化,后面传入的都还是以前的实例:
New SA
Test Controller: AspNetCore.Services.ServiceA
Test Controller: AspNetCore.Services.ServiceA
Test Controller: AspNetCore.Services.ServiceA
如果我们用 AddScoped
或者 AddTrancient
,每次访问 API
都会看到 ServiceA
被初始化了:
New SA
Test Controller: AspNetCore.Services.ServiceA
New SA
Test Controller: AspNetCore.Services.ServiceA
New SA
Test Controller: AspNetCore.Services.ServiceA
依赖后的生命周期
如果 ServiceA
是 transient
的,ServiceB
是 singleton
的,ServiceB
和 controller
都依赖 ServiceA
,请问第一次访问 controller
的路由,ServiceA
会被初始化几次?第二次访问呢?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public interface IServiceB { }
public class ServiceB : IServiceB
{
public ServiceB(IServiceA sa)
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IServiceA, ServiceA>();
services.AddSingleton<IServiceB, ServiceBz>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa, IServiceB sb)
{
Console.WriteLine($"Test Controller: {sa.GetType()} {sb.GetType()}");
}
}
第一次访问输出结果:
New SA
New SA
New SB
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
可以看到,ServiceA
因为是 transient
的,所以每次请求都会被初始化一次。而 ServiceB
是 singleton
的,虽然它依赖一个 transient
的 ServiceA
,但是初始化之后就不会再传入新的 ServiceA
了,在 singleton
的 ServiceB
中的 ServiceA
也是 singleton
的。
如果在 transient
的 ServiceA
中依赖一个 singleton
的 ServiceB
呢?
New SB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
singleton
的 ServiceB
不管在哪里取出,都是 singleton
的,虽然 ServiceA
和 controller
在多个请求中做了多次初始化,但是传入的都是同一个 ServiceB
实例。
多个依赖的初始化顺序
如果注册的时候是先 A 后 B,constructor
里是先 B 后 A,哪个会先被初始化?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public interface IServiceB { }
public class ServiceB : IServiceB
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
services.AddSingleton<IServiceB, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceB sb, IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetType()}");
}
}
输出结果:
New SB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
虽然注入依赖的顺序是 AB
,但是因为调用顺序是 BA
,所以会先初始化 B
再初始化 A
如果 B 的构造函数依赖了 A 呢?
public class ServiceB : IServiceB
{
public ServiceB(IServiceA sa)
{
Console.WriteLine($"New SB with sa:{sa.GetType()}");
}
}
输出结果:
New SA
New SB with sa:AspNetCore.Services.ServiceA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
此时会先把被依赖的 ServiceA
初始化完成再继续初始化 ServiceB
。
如果依赖注入的时候是先注入 B
再注入 A
呢?
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IServiceB, ServiceB>();
services.AddScoped<IServiceA, ServiceA>();
}
输出结果:
New SA
New SB with sa:AspNetCore.Services.ServiceA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
依赖注入的声明顺序并不重要,DI Container
会存储下 interface
和 class
的映射关系,在初始化的时候会根据依赖关系妥善处理。
一个接口多种实现
如果一个 interface
有多个实现类,并且都进行了注入,在 constructor
取出这个 interface
的时候会取到哪一个?多个实现类是否都会被初始化?
public interface IServiceA { }
public class ServiceA : IServiceA
{
public ServiceA()
{
Console.WriteLine("New SA");
}
}
public class ServiceB : IServiceA
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceA>();
services.AddSingleton<IServiceA, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa)
{
Console.WriteLine($"Test Controller: {sa.GetType()}");
}
}
输出结果:
New SB
Test Controller: AspNetCore.Services.ServiceB
一个接口多个实现,只会取出最后的一个实现来构造实例。其他实现类的构造方法不会被调用。DI Container
在存好 interface
和 class
的映射关系后,如果有新的实现就会覆盖掉前面的映射。
多个接口一个实现
如果一个接口有多个实现,并且都进行了单例的依赖注入,在取出实例的时候会被初始化几次?
public interface IServiceA { }
public interface IServiceB { }
public class ServiceB : IServiceA, IServiceB
{
public ServiceB()
{
Console.WriteLine("New SB");
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServiceA, ServiceB>();
services.AddSingleton<IServiceB, ServiceB>();
...
}
}
public class HelloController : ControllerBase
{
public WeatherForecastController(IServiceA sa, IServiceB sb)
{
Console.WriteLine($"Test Controller: {sa.GetType()} {sb.GetType()}");
}
}
输出结果:
New SB
New SB
Test Controller: AspNetCore.Services.ServiceB AspNetCore.Services.ServiceB
可以看到,AddSingleton
是针对 interface
的单例,而不是实现类的单例。对于 DI
来说,ServiceB
是对两种 interface
的实现类,会分别进行初始化。
后续
这些问题都是比较基础的依赖注入问题,希望对于依赖注入的学习起到抛砖引玉的作用。其中的一些理解分析也只是个人观点,如果有错误的地方欢迎指出。