ASP.NET Core 依赖注入基础测试题
作为一名 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 的实现类,会分别进行初始化。
后续
这些问题都是比较基础的依赖注入问题,希望对于依赖注入的学习起到抛砖引玉的作用。其中的一些理解分析也只是个人观点,如果有错误的地方欢迎指出。
如果希望深入的学习 ASP.NET Core 的依赖注入,推荐阅读 Microsoft.Extensions.DependencyInjection 源码 ,看完源码之后,很多疑惑和猜想便会自然得到解答。
参考资料: