一、依赖注入相关知识

1.1、依赖注入的原理和优点

  • 依赖注入(DI),是IOC控制反转思想 的实现。由一个DI容器,去统一管理所有的服务生命周期,服务的创建、销毁、获取,都是由DI容器去处理的。
  • 依赖注入,很大程度解耦了服务之间的依赖关系,服务之间依赖的是抽象(依赖的是 服务/服务接口 的 “类型”),而不是依赖具体的实现(服务不用关注他依赖的服务的创建,仅通过构造函数声明依赖的服务类型即可拿到依赖的服务实例,实际的服务实例是由容器去创建出来的)。
  • 在获取服务时,DI能够解析所有服务依赖关系树,将所有直接、间接 依赖的服务都创建了出来(不同的生命周期类型,创建的时机不同,见1.3),可以直接使用。
  • 优点:解耦、生命周期管理、提高可维护性、服务可替代性。(服务之间的依赖仅是类型,不关注依赖服务的创建,因此任何服务的改动,之间的影响是非常微小的,并且依赖的接口服务,在注册时候接口的实现可以直接替换。)
  • .net framework中 new() 创建服务,缺点:服务之间依赖的是具体的实现,依赖的服务需要手动创建出来,任何服务的改动,影响都是非常多的地方。当服务依赖层级很深的时候,外层服务使用底层服务,需要按个从低到把所有依赖的服务都创建出来。当其中某个服务构造函数发生变化时,所有用到他的地方,创建都需要更改,影响会很大,不利于维护。并且需要手动控制一些非托管资源服务的生命周期,需要注意内存泄漏的问题。

1.2、.Net 服务注册

  • 1.2.1、服务类型/服务接口类型 注册
// 将服务注册为接口
services.AddTransient<IMyService, MyService>(); // 泛型注册
services.AddScoped<IMyService, MyService>();
services.AddSingleton<IMyService, MyService>(); services.AddTransient<MyService>(); // 直接注册服务 service.AddTransient(typeof(IMyService),typeof(MyService)); // 类型注册
service.AddTransient(typeof(MyService));
  • 1.2.2、服务实例注册(仅适用于单例模式服务)
MyService myService = new MyService(); // 先new一个实例,然后注册为单例模式服务
services.AddSingleton(myService); // 直接注册
services.AddSingleton<IMyService>(myService); // 注册为接口
  • 1.2.3、ServiceDescriptor 使用服务描述类,注册
builder.Services.Add(new ServiceDescriptor(typeof(IMyService), typeof(MyService), ServiceLifetime.Transient)); // 注册的服务类型,服务的实现类型,服务的生命周期
builder.Services.Add(ServiceDescriptor.Transient(typeof(IMyService), typeof(MyService))); // 替换服务,已存在就替换;不存在,就新增
builder.Services.Replace(ServiceDescriptor.Transient(typeof(IMyService), typeof(MyService))); // Replace:如果IMyService服务已存在,就替换;不存在,就新增。

1.3、服务的三种生命周期类型,ServiceLifetime 枚举

Singleton 单例模式

  • 从DI中拿到该服务,任何时候拿到的都是同一个实例。
  • 单例服务,一般是程序运行时就创建,也可指定为第一次访问该服务时创建。单例服务 程序不会自动释放,一般情况下不需要释放;但需要注意内存问题,若包含非托管资源,需要注意避免内存泄漏问题。
  • 单例服务,不可直接依赖作用域服务。因为单例服务在程序运行时创建,这时候并没有任何服务作用域,直接依赖作用域服务会在程序运行时就抛异常。
  • 若单例服务,需要用到作用域服务,通常需要在合适的时机创建一个服务作用域,通过服务作用域的DI,拿到作用域服务。此作用域也需要在合适的时机手动释放掉。

Scoped 作用域模式

  • DI的同一个服务作用域中,拿到的是同一个实例。
  • 在服务作用域创建时,会将所有作用域服务都创建出来。作用域释放时候,会释放掉。
  • .Net Web程序 底层,所有控制器类都会自动注册为作用域服务。在每次发起http请求时,都会自动创建一个当前http请求的服务作用域,在请求结束时候,释放掉。

Transient 瞬时模式

  • 从DI中,每次获取该服务时,都会创建一个新实例,每次获取到该服务都不是同一实例。
  • 当服务不再被引用时,GC会自动回收释放;或者当服务作用域结束时候,也会释放掉瞬时服务。

1.6、服务注入、服务获取

  • 常规是构造函数注入,也有第三方依赖注入框架如:Autofac 等,实现了属性注入。但.net 推荐做法还是常规构造函数注入。
  • 通过 IServiceProvider,可以拿到所有注册的服务。
public class EFCoreController : ControllerBase
{
IMyTestService _myTestService;
IServiceProvider _serviceProvider; // DI服务提供器,是单例模式。通过他可以获取到DI中所有单例、瞬时服务。若在某个服务作用域内,则也可以获取到所有作用域服务。此处控制器中,是在http请求的服务作用域内。
public EFCoreController(
IServiceProvider serviceProvider,
IMyTestService myTestService)
{
_myTestService = serviceProvider;
_myTestService = myTestService;
_serviceProvider.GetService<T>(); // 从DI中获取服务,若服务未创建,获取不到,返回 null
_serviceProvider.GetRequiredService<T>(); // 从DI中获取服务,若服务未创建,获取不到,则会抛异常
}
}

1.5、依赖注入高级用法

  • 手动创建服务作用域,拿到作用域服务实例(通常用于在单例服务中,需要用到作用域服务的情况)

    (如:RabbitMQ的发布订阅,在BackgroundService后台任务(后台任务是单例模式)中注册RabbitMQ消费者,RabbitMQ发布事件,后台任务中的消费者接受消息时,可以通过消息中的 人员信息 等其他信息,手动创建一个服务作用域,并手动特殊处理一些有状态的服务,比如用户信息上下文等。在消息消费完成时,释放服务作用域。)
var app = builder.Build();

IServiceProvider rootServiceProvider = app.Services; // web程序运行时的DI,可以访问单例服务和瞬时服务,但无法访问作用域服务。
using (IServiceScope serviceScope = rootServiceProvider.CreateScope()) // IServiceProvider.CreateScope() 创建一个服务作用域,此时所有作用域服务都会创建出来。
{
IServiceProvider serviceProvider = serviceScope.ServiceProvider; // 使用服务作用域中的DI,可以访问 作用域服务
TestScopeService? testScopeService = serviceProvider.GetService<TestScopeService>(); // 测试拿到作用域服务实例
}

二、设计一个轻量级 自动依赖注入框架 【设计目标】

2.1、设计目标

  • 将想要注入的服务,标注一个特性,就可以直接注入到DI中
  • 该特性可以指定将该服务注册为某个接口的实现
  • 该特性可以指定该服务的生命周期类型
  • 在DI中,一键注入整个程序集中所有指定了该特性的服务

2.2、使用示例

2.2.1、在要注入的服务类上,标注CoreDI特性,指定要注册的服务类型、注册的服务生命周期类型

 [CoreDI(typeof(ITestSigletonService), ServiceLifetime.Singleton)] // 将该服务以接口形式注册到DI(这个类必须是该接口的实现);指定生命周期类型
public class TestSingletonService: ITestSigletonService
{
public void Test()
{
Console.WriteLine("单例服务");
}
} [CoreDI] // 不指定接口,直接注册该类;不指定生命周期类型,默认是作用域生命周期
public class TestScopeService
{
public void Test()
{
Console.WriteLine("作用域服务");
}
}

2.2.2、将当前程序集类所有标注了CoreDI特性的服务,全部注入到DI

builder.Services.AddCoreDIServices(typeof(TestSingletonService).Assembly);

三、设计一个轻量级 自动依赖注入框架 【代码实现】

3.1、CoreDIAttribute

/// <summary>
/// 特性 将服务注入到DI中
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CoreDIAttribute : Attribute
{
/// <summary>
/// 要注入的接口类型
/// </summary>
public Type? InterfaceType { get; private set; }
/// <summary>
/// 服务的生命周期
/// </summary>
public ServiceLifetime ServiceLifetime { get; private set; } public CoreDIAttribute(Type? interfaceType = null, ServiceLifetime serviceLifeTime = ServiceLifetime.Scoped)
{
if (!Enum.IsDefined(serviceLifeTime))
throw new Exception($"【CoreDI】 The Enum '{nameof(ServiceLifetime)}' value error.");
InterfaceType = interfaceType;
ServiceLifetime = serviceLifeTime;
}
}

3.2、CoreDIServiceExtensions

/// <summary>
/// 依赖注入 拓展
/// </summary>
public static class CoreDIServiceExtensions
{
/// <summary>
/// 将这些程序集中带有CoreDI特性的服务自动注入到DI容器
/// </summary>
public static void AddCoreDIServices(this IServiceCollection services, params Assembly[] assemblies)
{
// 找到这些程序集中包含CoreDI特性的所有服务,注册到DI
foreach (var assembly in assemblies)
{
Dictionary<Type, IEnumerable<CoreDIAttribute>> dic = assembly.GetTypes().ToDictionary(x => x, x => x.GetCustomAttributes<CoreDIAttribute>());
foreach (var item in dic)
{
Type type = item.Key;
IEnumerable<CoreDIAttribute> attributes = item.Value;
if (attributes.Count() == 0)
continue;
foreach (CoreDIAttribute di in attributes)
{
if (di.InterfaceType != null && !type.GetInterfaces().Any(x => x == di.InterfaceType)) // 若服务注册为接口形式,则必须是该接口的实现类
throw new Exception($"【CoreDI】 The Service '{type?.Name}' can not be resolving to the Service '{di.InterfaceType.Name}' ");
services.Replace(new ServiceDescriptor(di.InterfaceType ?? type, type, di.ServiceLifetime)); // 使用服务描述类 ServiceDescriptor 将服务注册到DI,存在就替换,不存在就新增;从CoreDI特性中拿到指定的生命周期类型
}
}
}
}
}

.Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的 自动依赖注入框架的更多相关文章

  1. Spring 依赖注入(基本注入和自动适配注入)

    Spring 依赖注入 Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系. 属性注入 构造注入 内部注入 自动装配 1.属性注入 IService: public in ...

  2. PHP反射机制实现自动依赖注入

    依赖注入又叫控制反转,使用过框架的人应该都不陌生.很多人一看名字就觉得是非常高大上的东西,就对它望而却步,今天抽空研究了下,解开他它的神秘面纱.废话不多说,直接上代码: /* * * * 工具类,使用 ...

  3. 新项目升级到JFinal3.5之后的改变-着重体验自动依赖注入

    最近,JFinal3.5发布,喜大普奔,我也应JBolt用户的需求,将JBolt进行了升级,实现可配置自动注入开启,支持JFinal3.5的项目生成.具体可以看:JBolt升级日志 这等工作做完后,我 ...

  4. 从EFCore上下文的使用到深入剖析DI的生命周期最后实现自动属性注入

    故事背景 最近在把自己的一个老项目从Framework迁移到.Net Core 3.0,数据访问这块选择的是EFCore+Mysql.使用EF的话不可避免要和DbContext打交道,在Core中的常 ...

  5. Spring学习笔记之 Spring IOC容器(一)之 实例化容器,创建JavaBean对象,控制Bean实例化,setter方式注入,依赖属性的注入,自动装配功能实现自动属性注入

    本节主要内容:       1.实例化Spring容器示例    2.利用Spring容器创建JavaBean对象    3.如何控制Bean实例化    4.利用Spring实现bean属性sett ...

  6. sql注入--双查询报错注入原理探索

    目录 双查询报错注入原理探索 part 1 场景复现 part 2 形成原因 part 3 报错原理 part 4 探索小结 双查询报错注入原理探索 上一篇讲了双查询报错查询注入,后又参考了一些博客, ...

  7. SLF4J其实只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖Log4j、logback等日志框架的。

    小结: 1.加层: 每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性. 为了解决这个问题,就是在日志框架和应用程序之间架设一个 ...

  8. sql server 关于表中只增标识问题 C# 实现自动化打开和关闭可执行文件(或 关闭停止与系统交互的可执行文件) ajaxfileupload插件上传图片功能,用MVC和aspx做后台各写了一个案例 将小写阿拉伯数字转换成大写的汉字, C# WinForm 中英文实现, 国际化实现的简单方法 ASP.NET Core 2 学习笔记(六)ASP.NET Core 2 学习笔记(三)

    sql server 关于表中只增标识问题   由于我们系统时间用的过长,数据量大,设计是采用自增ID 我们插入数据的时候把ID也写进去,我们可以采用 关闭和开启自增标识 没有关闭的时候 ,提示一下错 ...

  9. 几十行代码实现ASP.NET Core自动依赖注入

    在开发.NET Core web服务的时候,我们习惯使用自带的依赖注入容器来进行注入. 于是就会经常进行一个很频繁的的重复动作:定义一个接口->写实现类->注入 有时候会忘了写Add这一步 ...

  10. Spring 02多种注入方式和注解实现DI

    一.Bean作用域 spring容器创建的时候,会将所有配置的bean对象创建出来,默认bean都是单例的.代码通过getBean()方法从容器获取指定的bean实例,容器首先会调用Bean类的无参构 ...

随机推荐

  1. 关于failed to load resource 问题的处理

    问题: c++做插件,写了一个native class,继承于ue的类ActorComponent,而蓝图里也继承了这个c++ class,都在插件里,每次打开的时候就有这个错误: 之前的解决办法,复 ...

  2. DUILib的代码分析

    1.思路上用的是mfc的Message_MAP,而Control只是一个gdi render. 控件间使用的Message Map. 也就是说每个控件可以作为一个独立的个体

  3. CRC 循环冗余效验

    CRC循环冗余效验 利用多项式 x6 + x4 + x3,实际为使用模2除法来做的加密 常用crc多项式有 名称 生成多项式 数值式 简记式 标准引用 CRC-4 x4+x+1 0x1'3 0x3 I ...

  4. Hyperledger Fabric 2.x 环境搭建

    一.说明 区块链网络的核心是分布式账本,在这个账本中记录了网络中发生的所有交易信息. Hyperledger Fabric是一个是开源的,企业级的,带权限的分布式账本解决方案的平台.Hyperledg ...

  5. 不止于面向对象的SOLID原则

    SOLID原则是由人称"鲍勃大叔"的Rober C. Martin所提出来的.他用五个面向对象设计原则的首字母组成了SOLID,并使其得到了广泛传播.这五个原则罗列如下: 单一指责 ...

  6. 【Spring Data JPA】02 快速上手

    完成一个CRUD - 创建工程导入依赖坐标 - 配置Spring的配置文件 - 配置ORM的实体类,绑定映射关系 - 编写一个符合SpringDataJpa的dao接口 Maven依赖坐标 <p ...

  7. 寻路数据集 —— PathFinding数据集 —— Moving AI Lab. 实验室

    好几个做pathfinding的论文都是引用这个网站的数据集,不过这个网站的数据集的地图都是 .map 格式,这个类型的格式该如何打开还不知道. Moving AI Lab. 实验室的工作 地址: h ...

  8. SMU Autumn 2023 Round 5

    SMU Autumn 2023 Round 5 A. Everyone Loves to Sleep 把时间都转成分钟,然后存起来,二分找到离他睡觉点最近的一个时间段,减去他的睡觉点,如果最近的在第二 ...

  9. C# 使用特性的方式封装报文

    在编写上位机软件时,需要经常处理命令拼接与其他设备进行通信,通常对不同的命令封装成不同的方法,扩展稍许麻烦. 本次拟以特性方式实现,以兼顾维护性与扩展性. 思想: 一种命令对应一个类,其类中的各个属性 ...

  10. Polly服务熔断

    //自定义异常 public class MyException : Exception { public MyException(string? message) : base(message) { ...