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

简介: .Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的 自动依赖注入框架

一、依赖注入相关知识
1.1、依赖注入的原理和优点

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

1.2、.Net 服务注册

1.2.1、服务类型/服务接口类型 注册

// 将服务注册为接口
services.AddTransient(); // 泛型注册
services.AddScoped();
services.AddSingleton();

services.AddTransient(); // 直接注册服务

service.AddTransient(typeof(IMyService),typeof(MyService)); // 类型注册
service.AddTransient(typeof(MyService));

1.2.2、服务实例注册(仅适用于单例模式服务)

MyService myService = new MyService(); // 先new一个实例,然后注册为单例模式服务
services.AddSingleton(myService); // 直接注册
services.AddSingleton(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(); // 从DI中获取服务,若服务未创建,获取不到,返回 null
_serviceProvider.GetRequiredService(); // 从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(); // 测试拿到作用域服务实例
}
[box.2ni1.com)
[box.7080mir.com)
[box.congxx.com)
[box.furongmiaojia.com)
[box.geekchn.com)
[box.zizhu808.com)
二、设计一个轻量级 自动依赖注入框架 【设计目标】
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

///
/// 特性 将服务注入到DI中
///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class CoreDIAttribute : Attribute
{
///
/// 要注入的接口类型
///
public Type? InterfaceType { get; private set; }
///
/// 服务的生命周期
///
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

///
/// 依赖注入 拓展
///
public static class CoreDIServiceExtensions
{
///
/// 将这些程序集中带有CoreDI特性的服务自动注入到DI容器
///
public static void AddCoreDIServices(this IServiceCollection services, params Assembly[] assemblies)
{
// 找到这些程序集中包含CoreDI特性的所有服务,注册到DI
foreach (var assembly in assemblies)
{
Dictionary> dic = assembly.GetTypes().ToDictionary(x => x, x => x.GetCustomAttributes());
foreach (var item in dic)
{
Type type = item.Key;
IEnumerable 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特性中拿到指定的生命周期类型
}
}
}
}
}

相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
16天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2577 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
164 2
|
20天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1576 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
22天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
978 14
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
221 2
|
17天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
735 9