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 级别。

为什么 constructorpublic 呢?因为默认的访问级别是 private。依赖注入是由 ASP.NET Core 实现的,自然是无法访问 private 级别的构造方法的。

class 需不需要是 public 呢?不需要,因为通过方法调用的方式已经让 DI 获取到了 class,如果是 using namespace 的情况下访问 class,才需要 class 也是 public

生命周期

下面这段代码中,singletonIServiceAHelloController 所依赖,在项目启动之后,没有访问网页的情况下,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

依赖后的生命周期

如果 ServiceAtransient 的,ServiceBsingleton 的,ServiceBcontroller 都依赖 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 的,所以每次请求都会被初始化一次。而 ServiceBsingleton 的,虽然它依赖一个 transientServiceA,但是初始化之后就不会再传入新的 ServiceA 了,在 singletonServiceB 中的 ServiceA 也是 singleton 的。

如果在 transientServiceA 中依赖一个 singletonServiceB 呢?

New SB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB
New SA
Test Controller: AspNetCore.Services.ServiceA AspNetCore.Services.ServiceB

singletonServiceB 不管在哪里取出,都是 singleton 的,虽然 ServiceAcontroller 在多个请求中做了多次初始化,但是传入的都是同一个 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 会存储下 interfaceclass 的映射关系,在初始化的时候会根据依赖关系妥善处理。

一个接口多种实现

如果一个 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 在存好 interfaceclass 的映射关系后,如果有新的实现就会覆盖掉前面的映射。

多个接口一个实现

如果一个接口有多个实现,并且都进行了单例的依赖注入,在取出实例的时候会被初始化几次?

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 的实现类,会分别进行初始化。

后续

这些问题都是比较基础的依赖注入问题,希望对于依赖注入的学习起到抛砖引玉的作用。其中的一些理解分析也只是个人观点,如果有错误的地方欢迎指出。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值