IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    ASP.NET Core 依赖注入基础测试题

    Why\'s Blog发表于 2020-06-20 05:01:53
    love 0

    作为一名 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 源码 ,看完源码之后,很多疑惑和猜想便会自然得到解答。


    参考资料:

    • Microsoft.Extensions.DependencyInjection Source Code
    • Dependency Injection | ASP.NET Core Docs
    • Understanding Dependency Injection in .NET Core
    • ASP.NET Core Dependency Injection Deep Dive
    • ASP.NET Core Dependency Injection
    • .Net Core Dependency Injection
    • How to use dependency injection in ASP.Net Core


沪ICP备19023445号-2号
友情链接