.NET Core 核心知识点(四) -- 依赖注入(2)牛刀小试

项目中依赖注入的使用

多个服务类型在构造函数中注入使用


依赖注入的传染性

        依赖注入具有“传染性”,如果一个类的对象是通过DI创建的,那这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值;.NET的DI默认是构造函数注入。


  • 例子:假设我们要实现一个将字符串内容存储到数据库中的功能,并且打印相关的日志,这个时候,我们会建立三个服务,一个日志服务接口,一个获取数据库配置的服务,一个存储数据的服务类和他们各自的实现类,并在Controller中获取三个服务实现功能,我们可以知道,在存储数据的服务ICloudStorage服务中,我们需要一个配置服务类的对象来实现存储,然后在Controller中又需要CloudStorage服务,所以依赖注入是可以多层嵌套的。如下图

图:服务容器中注册相关的服务以及实现类

图:TestController功能模块图

  • 我们在Mian方法中把所有的服务类接口和对应的实现类注册到容器中,然后再在controller通过构造函数的形式获取服务即可,.net core 框架会自动分配构造函数中需要的服务类,不管是在controller层的ICloudStorage服务和CloudStorageImpl层的IConfig服务,都会自动获取;
class Program
    {
        static void Main(string[] args)
        {
            //框架处理代码,将服务注册到框架容器中
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<TestController>();
            services.AddScoped<ILog, LogImpl>();
            services.AddScoped<IConfig, ConfigImpl>();
            services.AddScoped<ICloudStorage, CloudStorageImpl>();

            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                var controller = sp.GetRequiredService<TestController>();
                controller.Test();
            }
        }
    }


    class TestController
    {
        //日志服务
        private readonly ILog _log;
        //云存储服务
        private readonly ICloudStorage _cloudStorage;

        //构造函数(通过构造函数依赖注入,获取服务实例)
        public TestController(ILog log, ICloudStorage cloudStorage)
        {
            _log = log;
            _cloudStorage = cloudStorage;
        }

        public void Test()
        {
            _log.Debugger("开始存储");
            _cloudStorage.Save("hello", "test");
            _log.Debugger("结束存储");
        }
    }


    #region 配置服务
    interface IConfig
    {
        string GetConnStr(string conn);
    }
    class ConfigImpl : IConfig
    {
        public string GetConnStr(string conn)
        {
            Console.WriteLine("获取到了数据库连接串123");
            return "123";
        }
    }
    #endregion

    #region 日志服务
    interface ILog
    {

        void Debugger(string msg);
    }

    class LogImpl : ILog
    {
        public void Debugger(string msg)
        {
            Console.WriteLine($"调试日志:{msg}");
        }
    }
    #endregion

    #region 云存储服务
    interface ICloudStorage
    {
        void Save(string content, string path);
    }

    class CloudStorageImpl : ICloudStorage
    {
        private readonly IConfig _config;

        public CloudStorageImpl(IConfig config)
        {
            _config = config;
        }

        public void Save(string content, string path)
        {
            var conn = _config.GetConnStr("conn");
            Console.WriteLine($"向数据库{conn}存储数据:{content},名字为:{path}");
        }
    } 
    #endregion

运行结果如下

所以,当从容器中获取了TestController的实例之后,框架会自动的给他需要的所有服务,包括服务里面需要的服务全部自动注入对象实例,这就是依赖注入的"传染性";

  • 依赖注入的好处

         万一哪天配置服务中的地址不从配置文件中读取,而是转变为从数据库中读取,那么我们的controller层的业务的代码不需要变,而是针对IConfig服务新增一个实例DbConfigImpl实现IConfig接口,然后在框架注册容器的代码中的实现类改成这个DbConfigImpl即可,可以降低模块之间的耦合度,代码修改如下:

可以看到,运行结果已经是新的实现类的逻辑: 

  • 观察上面的代码,我们把使用服务的过程交给了框架来处理,框架入口处将所有的服务和实现类注册到了ServicesCollection容器中,但是,这里也有一个不方便的点,就是框架的入口注册处必须知道所有的服务类,并且后期有变化,注册的代码都要随之变化,都要知道每个服务接口名以及它对应的实现类的名字,有没有更简便的方法呢?答案是:扩展方法

        我们在项目目录下新建一个扩展类LogImplExtension,命名空间改为:Microsoft.Extensions.DependencyInjection,然后增加一个静态的方法AddLogImpl,里面实现具体服务的注;这样,在程序框架的入口处,就可以直接使用services.AddLogImpl()方法;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class LogImplExtension
    {
        public static void AddLogImpl(this IServiceCollection services)
        {
            services.AddScoped<ILogService, LogImpl>();
        }
    }
}

  • 使用DI(依赖注入)实现多种配置方式的覆盖读取

比如现在有两个类型的配置,本地配置,远程服务上的配置,我们要实现自定义以何种配置优先原则,那么我们就可以通过服务注册优先顺序来实现

如下代码:

namespace DependencyInjectionDemo.Service.ServiceImpl
{
    public interface IConfigReader
    {
        public string GetValue(string name);
    }
}


 public class LayeredConfigReadImpl : IConfigReader
    {
        private readonly IEnumerable<IConfigService> _configServices;

        public LayeredConfigReadImpl(IEnumerable<IConfigService> configServices)
        {
            _configServices = configServices;
        }

        public string GetValue(string name)
        {
            string value = null; 
            foreach (var service in _configServices)
            {
                value = GetValue(name)!=null?GetValue(name):null;
            }
            return value;
        }
    }

同时定义一个扩展方法来实现注册

namespace Microsoft.Extensions.DependencyInjection
{
    public static class LayeredConfigReadExtension
    {
        public static void AddLayeredConfigReadImpl(this IServiceCollection services)
        {
            services.AddScoped<IConfigReader, LayeredConfigReadImpl>();
        }
    }
}

然后再框架入口的时候,调整配置服务的注册顺序,就可以实现配置覆盖的功能

        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            //services.AddScoped<ILogService, LogImpl>();
            services.AddLogImpl();
            //1.先从环境变量中获取配置
            services.AddScoped<IConfigService, EnvVarConfigServiceImpl>();
            //2.然后再从指定目录下的文件中获取相关配置
            services.AddScoped<IConfigService>(s => new txtConfigServiceImpl() {  FilePath = "C:\\Users\\liyumin\\Desktop\\mail.txt" });
            
            services.AddLayeredConfigReadImpl();
            services.AddScoped<IMailService, MailServiceImpl>();

            using (ServiceProvider sp = services.BuildServiceProvider())
            {
                var mailService = sp.GetRequiredService<IMailService>();
                mailService.Send("hello","liyumin","这是我们的邮件内容");
            }
        }

 运行结果如下

 

此时如果我们交换配置服务的注册顺序,就会发现,最后读取的是电脑环境变量的值

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值