net core 依赖注入

概念

依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)

控制反转(IOC):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)

依赖注入(DI):一种反转流、依赖和接口的方式(DIP的具体实现方式)

IOC容器:依赖注入的框架,用来映射依赖,管理对象创建和依赖周期

依赖倒置原则 DIP

在生活中,之前有看到这样的场景,有一个ATM机,只要有一张卡,就可以插入取钱操作,在这个场景下ATM相当于高层模块,而银行卡是下层模块,ATM不依赖某个银行卡,只需要定义好规格参数,就可以正常使用。软件也可以借用这个概念。依赖倒置原则,转换了依赖,高层不依赖与下层模块,下层模块依赖于高层模块。也就是高层模块定义接口,下层模块实现接口。

这种方式的优点 

1 程序更柔韧 修改一部分代码而不影响其他模块

2 程序更健壮 修改代码不会让程序崩溃

3 程序更高效 每一个组件轻松耦合,且可复用,提升开发速度

控制反转 IOC

DIP是一种设计原则,他告诉你俩个模块如何相互依赖,但他没有告诉你如何实现

IOC是一种设计模式,他告诉你如何做,来解除相互依赖的耦合,控制反转,它为互相依赖的组件提供抽象,将依赖(底层模块)交给第三方管理(系统)控制,即依赖对象不在被依赖的模块的类中new来获取。

软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。一些常见的原则,比如DRY、OCP、DIP等。
软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等

举个例子,加入我们今天订单需要发送短信

 public class EmailNotification
 {
     public void SendEmail(Order order)
     {
         // 发送邮件的代码...
     }
 }

public class OrderProcess
  {
      private EmailNotification _notification;

      public OrderProcess()
      {
          _notification = new EmailNotification();
      }

      public void ProcessOrder(Order order)
      {
          // 订单处理逻辑...

          // 发送通知
          _notification.SendEmail(order);
      }
  }

此时,OrderProcess订单处理类,直接依赖于EmailNotification邮件通知类,我们想改变通知方式,就要更改OrderProcess类,这违反了开闭原则,并且这个设计不够灵活和可扩展

新增短信通知只能新增一个实现类,修改OrderProcess类中的发送消息的对象,如果还要新增更多的通知方式呢,显然这不是一个良好的设计,组件之间相互耦合,扩展性比较差,它也违背了DIP原则,高层模块OrderProcess不应该依赖于下层模块,两者都应该依赖于抽象,我们可以使用IOC来优化代码,可以通过依赖注入和服务定位来实现,接下来我们使用依赖注入DI,查看如何使用

依赖注入DI

控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。

依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象

为了应用依赖倒置原则,我们应该定义接口来进行通知:

  public interface INotification
  {
      public void SentMsg(Order order);
  }

  public class EmailNotification : INotification
  {
      public void SentMsg(Order order)
      {
          // 发送邮件的代码...
      }
  }

public class SmsNotification : INotification
{
       public void SentMsg(Order order)
       {
           // 发送短信的代码...
       }
   }

public class OrderProcessTwo
{
    public INotification _notification;
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="notification"></param>
    public OrderProcessTwo(INotification notification)
    {
        _notification = notification;
    }

    public void ProcessOrder(Order order)
    {
        // 订单处理逻辑...

        // 发送通知
        _notification.SentMsg(order);
    }
}

//示例

// 使用EmailNotification发送通知
INotification emailNotification = new EmailNotification();
OrderProcessor processorWithEmail = new OrderProcessor(emailNotification);
processorWithEmail.ProcessOrder(new Order());

// 使用SmsNotification发送通知
INotification smsNotification = new SmsNotification();
OrderProcessor processorWithSms = new OrderProcessor(smsNotification);
processorWithSms.ProcessOrder(new Order());

此时我们想要更改消息发送方式,只需要在外部更改其他对象,就可以实现更换消息发送方式,而不需要修改OrderProcessor内部代码,此时已经体现了IOC的巧妙

此外还有属性注入和接口注入设计基本一致,可以自行了解

服务定位的实现方式

public class ServiceLocator
{
    private static IDictionary<object, object> services = new Dictionary<object, object>();

    public static void AddService<T>(T service)
    {
        services[typeof(T)] = service;
    }

    public static T GetService<T>()
    {
        return (T)services[typeof(T)];
    }
}

public class OrderProcessThree
  {
      private INotification _notification;

      public OrderProcessThree()
      {
          // 从服务定位器中获取需要的通知服务
          _notification = ServiceLocator.GetService<INotification>();
      }

      public void ProcessOrder(Order order)
      {
          // 处理订单的逻辑
          // ...
          _notification.SentMsg(order);
      }
  }

依赖注入DI的示例实例化十分清晰,它通过其构造函数定义它所依赖的INotification类型的服务

而服务定位SL实例化过程中并不直接指定其所需的服务,OrderProcessor需要从ServiceLocator中查询所需要的服务。这种如果没有适当配置,会出现问题,依赖也不像DI那样明显,推荐使用DI

介绍完这些那么在netcore中如何应用呢

net core基本用法

在 .NET Core 平台下,有一套自带的轻量级 Ioc 框架,如果是 ASP.NET Core 项目,更是在使用主机的时候自动集成了进去,我们在 startup 类中的 ConfigureServices 方法中的代码就是往容器中配置依赖注入关系,如果是控制台项目的话,还需要自己去集成。除此之外,.NET 平台下还有一些第三方依赖注入框架,如 Autofac、Unity、Castle Windsor 等。

推荐原生注入和autofac注入先介绍下原生注入

服务

依赖项注入术语中,服务通常是指向其他对象提供服务的对象,既可以作为其他类的依赖项,也可能依赖于其他服务。服务是 Ioc 容器管理的对象。

三种生命周期

AddSingleton 单例模式:程序启动到关闭,对象只有这一个

AddTransient 瞬时模式:服务在每次请求时被创建

AddScoped 区域模式:每一次使用到服务时都会创建一个新的实例,每一次对该依赖的获取都是一个新实例

语法

在program注入,net5可能会有startup分开写了而已,不要重复注入,会产生多个实例的副本,可能会出现意料之外的问题

builder.Services.AddTransient(typeof(IMyService), typeof(MyService));
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddSingleton<IMyService, MyService>();

控制器中构造方法调用

private IMyService _service; 
public ValuesController(IMyService service)
  {
      this._service = service;
  }

你可以在program中移除或清空服务,可以使用 Replace 和 Remove 

// 将容器中注册的IRabbit实现替换为 Rabbit1
builder.Services.Replace(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
// 从容器中 IRabbit 注册的实现 Rabbit1
builder.Services.Remove(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
// 移除 IRabbit服务的所有注册
builder.Services.RemoveAll<IRabbit>();
// 清空容器中的所有服务注册
builder.Services.Clear();

autofac使用

引入包文件 Autofac.Extras.DynamicProxy(Autofac的动态代理,它依赖Autofac,所以可以不用单独引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的扩展)

如何在自带对象中使用,可以使用工厂模式创建

var builder = WebApplication.CreateBuilder(args); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //创建服务提供工厂

builder.Host.ConfigureContainer<ContainerBuilder>(builder => 
{

        //autofac 相关代码

        //.....

        //推荐写在这里边
         var dlls = builder.Configuration.GetSection("Dll").Get<List<string>>();
          //注册 推荐这种方式 实现写在方法中 dlls对象是后续批量注入的功能
          autofacBuilder.RegisterModule(new AutofacModule(dlls));
});

或者这种方式

// 配置AutoFac
var containerBuilder = new ContainerBuilder();
//autofac注入代码..........

// 构建AutoFac容器
var container = containerBuilder.Build();

新建一个AutofacModuleRegister模块,继承自Autofac.Module,在Load方法中注册后续依赖注入过程中需要使用的的类型

 public class AutofacModuleRegister : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {

                //你的代码.......
            builder.RegisterType<ArticleService>().As<IArticleService>();
        }
    }

autofac批量注入
服务程序集注入方式 —— 未解耦

这种方式需要添加对项目的引用

 public class AutofacModule : Module
  {
      private List<string> _dlls = new List<string>();

      public AutofacModule(List<string> dlls)
      {
          _dlls = dlls;
      }
      protected override void Load(ContainerBuilder builder)
      {

           //要记得!!!这个注入的是实现类层,不是接口层!不是 IServices
           var assemblysServices = Assembly.Load("YouProject.Service");

           //指定已扫描程序集中的类型注册为提供所有其实现的接口。
     autofacBuilder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();

      }
  }

程序集注入 —— 实现层级引用的解耦

 public class AutofacModule : Module
  {
      private List<string> _dlls = new List<string>();

      public AutofacModule(List<string> dlls)
      {
          _dlls = dlls;
      }
      protected override void Load(ContainerBuilder builder)
      {
          var basePath = AppContext.BaseDirectory;
          var Type = new List<Type>();
       

          List<Assembly> servicesAssembly = new List<Assembly>();
          foreach (var item in _dlls)
          {
              var dllFile = Path.Combine(basePath, item);
              if (!File.Exists(dllFile))
              {
                  throw new Exception($"{item}丢失,检查appsettings.json中Autofac配置;如果项目的bin目录下没有该文件,检查是否有引用项目。");
              }
              servicesAssembly.Add(Assembly.LoadFrom(dllFile));
          }

          // 支持属性注入依赖重复,消费队列单独注入
          builder.RegisterAssemblyTypes(servicesAssembly.ToArray())
              .AsImplementedInterfaces()
              .InstancePerDependency()
              .EnableInterfaceInterceptors() // 引用Autofac.Extras.DynamicProxy;
              .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)
              .InterceptedBy(Type.ToArray()); //允许将拦截器服务的列表分配给注册
      }
  }

Aop日志记录

autofac中有类似中间件的功能不需要,可以实现一些对请求日志记录的功能

首先创建一个类,继承IInterceptor接口,写一些简单记录日志http请求参数

 /// <summary>
 ///拦截器 LogAOP 继承IInterceptor接口
 /// </summary>
 public class RecordAop : IInterceptor
 {
     /// <summary>
     /// 实例化IInterceptor唯一方法 
     /// </summary>
     /// <param name="invocation">包含被拦截方法的信息</param>
     public void Intercept(IInvocation invocation)
     {
         //记录被拦截方法信息的日志信息
         var dataIntercept =  $"当前执行方法:{invocation.Method.Name}  \r\n" +
             $"参数是: {JsonHelper.ObjectToJson(invocation.Arguments)} \r\n";
         //在被拦截的方法执行完毕后 继续执行当前方法
         invocation.Proceed();

         dataIntercept += ($"被拦截方法执行完毕,返回结果:{JsonHelper.ObjectToJson(invocation.ReturnValue)} \r\n");

         #region 输出到当前项目日志
         SerilogHelper.WriteLog("RecordAop", "Intercept", dataIntercept);

         #endregion

     }
 }

添加到autofac容器中,在AutofacModule类中新增

    var Type = new List<Type>();
    builder.RegisterType<RecordAop>();//可以直接替换其他拦截器!一定要把拦截器进行注册
    Type.Add(typeof(RecordAop));

实现缓存功能

定义缓存类和接口

 /// <summary>
 /// 简单的缓存接口,只有查询和添加,以后会进行扩展
 /// </summary>
 public interface ICaching
 {
     public object Get(string cacheKey);

     public void Set(string cacheKey, object cacheValue);
 }

 /// <summary>
 /// 实例化缓存接口ICaching
 /// </summary>
 public class MemoryCaching : ICaching
 {
     //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了
     private IMemoryCache _cache;
     //还是通过构造函数的方法,获取
     public MemoryCaching(IMemoryCache cache)
     {
         _cache = cache;
     }

     public object Get(string cacheKey)
     {
         return _cache.Get(cacheKey);
     }

     public void Set(string cacheKey, object cacheValue)
     {
         _cache.Set(cacheKey, cacheValue, TimeSpan.FromMilliseconds(2000));
     }
 }

定义一个缓存拦截器

    /// <summary>
    /// 面向切面的缓存使用
    /// </summary>
    public class CacheAOP : IInterceptor
    {
        //通过注入的方式,把缓存操作接口通过构造函数注入
        public readonly ICaching _cache;
        public CacheAOP(ICaching cache)
        {
            _cache = cache;
        }

        public void Intercept(IInvocation invocation)
        {
            //获取自定义缓存键
            var cacheKey = CustomCacheKey(invocation);
            //根据key获取相应的缓存值
            var cacheValue = _cache.Get(cacheKey);
            if (cacheValue != null)
            {
                //将当前获取到的缓存值,赋值给当前执行方法
                invocation.ReturnValue = cacheValue;
                return;
            }
            //去执行当前的方法
            invocation.Proceed();
            //存入缓存
            if (!string.IsNullOrWhiteSpace(cacheKey))
            {
                _cache.Set(cacheKey, invocation.ReturnValue);
            }
        }

        //自定义缓存键
        private string CustomCacheKey(IInvocation invocation)
        {
            var typeName = invocation.TargetType.Name;
            var methodName = invocation.Method.Name;
            var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,我最多需要三个即可

            string key = $"{typeName}:{methodName}:";
            foreach (var param in methodArguments)
            {
                key += $"{param}:";
            }

            return key.TrimEnd(':');
        }
        //object 转 string
        private string GetArgumentValue(object arg)
        {
            if (arg is int || arg is long || arg is string)
                return arg.ToString();

            if (arg is DateTime)
                return ((DateTime)arg).ToString("yyyyMMddHHmmss");

            return "";
        }
    }

添加到autofac容器中,在AutofacModule类中新增

builder.RegisterType<MemoryCaching>().As<ICaching>();// 服务层注册
var Type = new List<Type>();
builder.RegisterType<CacheAOP>();
Type.Add(typeof(CacheAOP));

运行后可以查看效果,不要放到service层中,不然缓存aop会出错,你对service定义的缓存拦截器,你定义到service会出现循环引用

最后,可以运行测试下代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值