你有把依赖注入玩坏?

【导读】自从.NET Core给我们呈现了依赖注入,在我们项目中到处充满着依赖注入,虽然一切都已帮我们封装好,但站在巨人的肩膀上,除了凭眺远方,我们也应平铺好脚下的路

使用依赖注入不仅仅只是解耦,而且使代码更具维护性,同时我们也可轻而易举查看依赖关系,单元测试也可轻松完成,本文我们来聊聊依赖注入,文中示例版本皆为5.0。

浅谈依赖注入

在话题开始前,我们有必要再提一下三种服务注入生命周期, 由浅及深再进行讲解,基础内容,我这里不再多述废话

Transient(瞬时):每次对瞬时的检索都会创建一个新的实例。

Singleton(单例):仅被实例化一次。此类型请求,总是返回相同的实例。

Scope(范围):使用范围内的注册。将在请求类型的每个范围内创建一个实例。

如果已用过.NET Core一段时间,若对上述三种生命周期管理的概念没有更深刻的理解,我想有必要基础回炉重塑下。为什么?至少我们应该得出两个基本结论

其一:生命周期由短到长排序,瞬时最短、范围次之、单例最长

只要做过Web项目,关于第一点就很好理解,首先我们只对瞬时和范围作一个基本的概述,关于单例通过实际例子来阐述,我们理解会更深刻

若为瞬时:那么我们每次从容器中获取的服务将是不同的实例,所以名为瞬时或短暂

若为范围:在ASP.NET Core中,针对每个HTTP请求都会创建DI范围,当在HTTP请求中(在中间件,控制器,服务或视图中)请求服务,并且该服务注册为范围服务时,如果在请求中多次请求相同类型的请求,则使用相同实例。例如,如果在控制器,服务和视图中注入了范围服务,则将返回相同的实例。随着另一个HTTP请求的流,使用了不同的实例,请求完成后,将处理(释放)范围

其二:被注入的服务应与注入的服务应具有相同或更长的生命周期

从概念上看貌似有点拗口,通过日常生活举个栗子则秒懂,假设有两个桶,一个小桶和一个大桶,我们能将小桶装进大桶,但不能将大桶装进小桶。

专业一点讲,比如一个单例服务可以被注入瞬时服务,但是一个瞬时服务不能被注入单例服务,因为单例服务比瞬时服务生命周期更长,若瞬时服务被注入单例服务,那么势必将延长瞬时服务生命周期,因违背大前提,将会引起异常

public interface ISingletonDemo1
{
}public class SingletonDemo1 : ISingletonDemo1
{private readonly IScopeDemo1 _scopeDemo1;public SingletonDemo1(IScopeDemo1 scopeDemo1){_scopeDemo1 = scopeDemo1;}
}public interface IScopeDemo1
{
}
public class ScopeDemo1 : IScopeDemo1
{
}

我们在Web中进行演示,然后在Startup中根据其接口名进行注册,如下:

services.AddSingleton<ISingletonDemo1, SingletonDemo1>();
services.AddScoped<IScopeDemo1, ScopeDemo1>();

从理论上讲肯定是这样,好像有点太绝对,抱着自我怀疑的态度,于是乎,我们在控制台中验证一下看看

static void Main(string[] args)
{var services = new ServiceCollection();services.AddSingleton<ISingletonDemo1, SingletonDemo1>();services.AddScoped<IScopeDemo1, ScopeDemo1>();services.BuildServiceProvider();
}

然鹅并没有抛出任何异常,注入操作都一样,有点懵,看看各位看官能否给个合理的解释,在控制台中并不会抛出异常......

深谈依赖注入

关于依赖注入基础和使用准则,我建议大家去看看,还是有很多细节需要注意

依赖注入设计准则

https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines

在.NET Core中使用依赖注入

https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage

比如其中提到一点,服务容器并不会创建服务,也就是说如下框架并没有自动处理服务,需要我们开发人员自己负责处理服务的释放

public void ConfigureServices(IServiceCollection services)
{services.AddSingleton(new ExampleService());// ...
}

假设我们有一个控制台命令行项目,我们通过引入依赖注入单例做一些操作

public interface ISingletonService
{void Execute();
}public class SingletonService : ISingletonService
{public void Execute(){}
}

紧接着控制台入口点演变成如下这般

static void Main(string[] args)
{var serviceProvider = new ServiceCollection().AddSingleton<ISingletonService, SingletonService>().BuildServiceProvider();var app = serviceProvider.GetService<ISingletonService>();app.Execute();
}

若在执行Execute方法里面做了一些临时操作,比如创建临时文件,我们想在释放时手动做一些清理,所以我们实现IDisposable接口,如下:

public class SingletonService : ISingletonService, IDisposable
{public void Execute(){}public void Dispose(){// do something}
}

然后项目上线,我们可能会发现内存中大量充斥着该实例,从而最终导致内存泄漏,这是为何呢?


我们将服务注入到容器中,容器将会自动管理注入实例的释放,根据如下可知

最终我们通过如下方式即可解决上述内存泄漏问题

using (var serviceProvider = new ServiceCollection().AddSingleton<ISingletonService, SingletonService>().BuildServiceProvider())
{var app = serviceProvider.GetService<ISingletonService>();app.Execute();
}

是不是有点懵,接下来我们来深入探讨三种类型生命周期释放问题,尤其是单例,首先我们通过注入自增长来标识每一个注入服务,便于查看释放时机对应标识

public interface ICountService
{int GetCount();
}public class CountService : ICountService
{private int _n = 0;public int GetCount() => Interlocked.Increment(ref _n);
}

接下来则是定义瞬时、范围、单例服务,并将其进行注入,如下:

public interface ISingletonService
{void Say();
}public class SingletonService : ISingletonService, IDisposable
{private readonly int _n;public SingletonService(ICountService countService){_n = countService.GetCount();Console.WriteLine($"构造单例服务-{_n}");}public void Say() => Console.WriteLine($"调用单例服务-{_n}");public void Dispose() => Console.WriteLine($"释放单例服务-{_n}");}public interface IScopeSerivice
{void Say();
}public class ScopeSerivice : IScopeSerivice, IDisposable
{private readonly int _n;public ScopeSerivice(ICountService countService){_n = countService.GetCount();Console.WriteLine($"构造范围服务-{_n}");}public void Say() => Console.WriteLine($"调用范围服务-{_n}");public void Dispose() => Console.WriteLine($"释放范围服务-{_n}");
}public interface ITransientService
{void Say();
}public class TransientService : ITransientService, IDisposable
{private readonly int _n;public TransientService(ICountService countService){_n = countService.GetCount();Console.WriteLine($"构造瞬时服务-{_n}");}public void Say() => Console.WriteLine($"调用瞬时服务-{_n}");public void Dispose() => Console.WriteLine($"释放瞬时服务-{_n}");
}

最后在入口注入并调用相关服务,再加上最后打印结果,应该挺好理解的

static void Main(string[] args)
{var services = new ServiceCollection();services.AddSingleton<ICountService, CountService>();services.AddSingleton<ISingletonService, SingletonService>();services.AddScoped<IScopeSerivice, ScopeSerivice>();services.AddTransient<ITransientService, TransientService>();using (var serviceProvider = services.BuildServiceProvider()){using (var scope1 = serviceProvider.CreateScope()){var s1a1 = scope1.ServiceProvider.GetService<IScopeSerivice>();s1a1.Say();var s1a2 = scope1.ServiceProvider.GetService<IScopeSerivice>();s1a2.Say();var s1b1 = scope1.ServiceProvider.GetService<ISingletonService>();s1b1.Say();var s1c1 = scope1.ServiceProvider.GetService<ITransientService>();s1c1.Say();var s1c2 = scope1.ServiceProvider.GetService<ITransientService>();s1c2.Say();Console.WriteLine("--------------------------------释放分界线");}Console.WriteLine("--------------------------------结束范围1");Console.WriteLine();using (var scope2 = serviceProvider.CreateScope()){var s2a1 = scope2.ServiceProvider.GetService<IScopeSerivice>();s2a1.Say();var s2b1 = scope2.ServiceProvider.GetService<ISingletonService>();s2b1.Say();var s2c1 = scope2.ServiceProvider.GetService<ITransientService>();s2c1.Say();}Console.WriteLine("--------------------------------结束范围2");}Console.ReadKey();
}

我们描述下整个过程,通过容器创建一个scope1和scope2,并依次调用范围、单例、瞬时服务,然后在scope和scope2结束时,释放瞬时、范围服务。最终在容器结束时,才释放单例服务

从获取、释放以及打印结果来看,我们可以得出两个结论

其一:每一个scope被释放时,瞬时和范围服务都会被释放,且释放顺序为倒置

其二:单例服务在根容器释放时才会被释放

有了上述结论2不难解释我们首先给出的假设控制台命令行项目为何会导致内存泄漏,若非手动实例化,实例对象生命周期都将由容器管理,但在构建容器时,我们并未释放(使用using),所以当我们手动实现IDisposable接口,通过实现Dispose方法进行后续清理工作,但并不会进入该方法,所以会导致内存泄漏

看到这里,我相信有一部分童鞋会有点大跌眼镜,因为和沉浸在自我想象中的样子不一致,实践是检验真理的唯一标准,最后我们对依赖注入做一个总结

在容器中注册服务,容器为了处理所有注册实例,容器会跟踪所有对象,即使是瞬时服务,也并不是检索完后,就一次性进行释放,它依然在容器中保持“活跃”状态,同时我们也应防止GC释放超出其范围的瞬时服务

即使是瞬时服务也和作用域(scope)有关,通过引入作用域而进行释放,否则根容器会一直保存其实例对象,造成巨大的内存损耗,甚至是内存泄漏

???? 瞬时服务可作为注册服务的首选方法,范围和单例用于共享状态


???? 每一个scope被释放时,瞬时和范围服务都会被释放,且释放顺序为倒置

???? 单例服务从不与作用域关联,它们与根容器关联,并在处置根容器时处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/305567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 表与表之间的条件比对_值得收藏 | 一份最完整的MySQL规范

一、数据库命令规范所有数据库对象名称必须使用小写字母并用下划线分割所有数据库对象名称禁止使用MySQL保留关键字(如果表名中包含关键字查询时&#xff0c;需要将其用单引号括起来)数据库对象的命名要能做到见名识意&#xff0c;并且最后不要超过32个字符临时库表必须以tmp_为…

asp.net core服务中的限流

使用了AspNetCoreRateLimit三方库&#xff0c;starup.cs配置如下。using AspNetCoreRateLimit; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Exte…

sublime text html乱码,Sublime Text 2中文显示乱码的解决方法

Sublime Text 2中文显示乱码的解决方法发布时间&#xff1a;2014-05-12 15:30:14 作者&#xff1a;佚名 我要评论这篇文章主要介绍了Sublime Text 2中文显示乱码的解决方法,需要的朋友可以参考下1、安装Sublime Package Control。在Sublime Text 2上用Ctrl&#xff5e;打开…

2020 中国开源年度报告

点击上方“开源社”关注我们| 编辑&#xff1a;沈于蓝1前言撰写这篇前言&#xff0c;我们的心情一直很复杂&#xff0c;很难用某种词汇来概括。在心里升起的一句话是&#xff1a;“这是最好的时代&#xff0c;这是最坏的时代”。也许多年以后&#xff0c;我们回看 2020 年&…

python实验总结心得体会_Python,Pyvisa操作Agilent 86140x系列OSA

1&#xff0c;背景本人做光学的。。。最近研究实验偏向通信做WDM实验的时候发现能用实验室TCP/IP通信的光谱仪(OSA)都被用了&#xff0c;翻翻找找只在角落找到一台Agilent 68146B的OSA。。。就是下图这个货&#xff0c;想要捞取光谱数据&#xff0c;你看到那个软盘接口了吗。。…

cgcs2000高斯平面直角坐标_如何巧妙记忆高斯积分

高斯积分作为一种特殊的反常积分&#xff0c;其应用范围相当广泛&#xff0c;无论是在概率论中所引入的高斯分布&#xff08;亦称正态分布&#xff09;&#xff0c;还是在统计物理中的相关应用&#xff0c;都表明其有着至关重要的作用。下面我们来介绍一种记忆高斯积分的方法&a…

基于.NET Core的优秀开源项目合集

开源项目非常适合入门&#xff0c;并且可以作为体系结构参考的好资源, GitHub中有几个开源的.NET Core项目&#xff0c;这些项目将帮助您使用不同类型的体系结构和编码模式来深入学习 .NET Core技术, 本文列出了不同类别的优秀的开源项目。???? eShopOnContainerseShopOnCo…

提高计算机软件速度的方法,小白看过来!提高电脑速度8种实用方法

当电脑使用一段时间之后&#xff0c;常常会遇到运行速度逐渐变缓的情况&#xff0c;甚至卡顿死机&#xff0c;非常影响体验。那么有什么办法可以快速便捷的提高电脑运行速度呢?下面就教你8种办法&#xff0c;让电脑恢复飞速运行状态。1.关闭自动更新电脑总是自动更新&#xff…

C# Task 循环任务_C# Task.Run调用外部参数

首先讲一下&#xff1a;c# Task启动带参数和返回值的方法&#xff1a;Task启动带参数Task.Run(() > test("123"));public void Test(string s){...todo..}Task启动带参数和返回值的方法var s Task.Run(() > isTest("ss"));var t s.Result;private …

如何向K8s,Docker-Compose注入镜像Tag

最近在做基于容器的CI/CD, 一个朴素的自动部署的思路是&#xff1a;从Git Repo打出git tag&#xff0c;作为镜像Tagssh远程登录到部署机器向部署环境注入镜像Tag&#xff0c;拉取镜像&#xff0c;重新部署下面分享我是如何在K8s、docker-compose中注入镜像Tag&#xff1f;k8s熟…

职称计算机还用考试,职称计算机考试注意事项

职称计算机考试注意事项一、注意事项首先&#xff0c;由于考试是模块化设计&#xff0c;所以在报考时要选择自己熟悉的科目。切忌好高骛远&#xff0c;选择那些自己日常使用机会不多、并无把握的科目。许多人在考试时选择了自己工作中最常用的Windows XP、Word 2003、PowerPoin…

python安装地是什么_如何安装python

展开全部 Python是跨平台的&#xff0c;可以运行在2113Windows、Mac和各种Linux/Unix系统上5261。在Windows上写Python程序&#xff0c;放到Linux上也是能够4102运行的。 要开始学习1653Python编程&#xff0c;首先就得把Python安装到电脑里。安装后&#xff0c;会得到Python解…

ABP vNext 实现租户Id自动赋值插入

背景在使用ABP vNext过程中&#xff0c;因为我们的用户体系庞大&#xff0c;所以一直与其他业务同时开发&#xff0c;在开发其他业务模块时&#xff0c;我们一直存在着误区&#xff1a;认为ABP vNext 自动处理了数据新增时的租户Id&#xff08;TenantId&#xff09;的自动赋值插…

深入研究.NET 5的开放式遥测

OpenTelemetry 介绍OpenTelemetry是一种开放的源代码规范&#xff0c;工具和SDK&#xff0c;用于检测&#xff0c;生成&#xff0c;收集和导出遥测数据&#xff08;指标&#xff0c;日志和跟踪&#xff09;,开放遥测技术得到了Cloud Native Computing Foundation&#xff08;CN…

.NET5实战千万并发,性能碾压各版本,云原生时代,.NET5为王!

在移动互联网时代掉队的.NET&#xff0c;当下正凭借着.NET5的开源跨平台以及容器友好&#xff0c;在云原生时代正在重铸辉煌。而作为.NET开发者&#xff0c;新年跳槽季的高并发问题会更多了&#xff0c;因为高并发能牵扯出太多问题&#xff0c;接口响应超时、CPU负载升高、GC频…

html点击按钮计算两个输入框的和_小程序计算报价功能介绍

一、使用场景用户可在管理后台设置计算值和运算公式&#xff0c;访客输入对应计算值&#xff0c;即可实现自动计算出结果&#xff0c;并提供相关的咨询入口&#xff0c;适用于装修、建材、房贷、车险等行业的报价行为。二、功能版本限制小程序至尊版可开启和使用计算报价功能。…

用python画树_Python+Turtle动态绘制一棵树实例分享

本文实例主要是对turtle的使用&#xff0c;实现Pythonturtle动态绘制一棵树的实例&#xff0c;具体代码&#xff1a; # drawtree.py from turtle import Turtle, mainloop def tree(plist, l, a, f): """ plist is list of pens l is length of branch a is hal…

[ASP.NET2.0] asp.net在ie7中使用FileUpload上传前预览图片 [ZT]

asp.net在ie7中使用FileUpload上传前预览图片 因为安全性问题&#xff0c;IE7禁用了image控件引用本地图片&#xff0c;为了这个问题郁闷了好几天&#xff0c;终于找到了解决方案&#xff0c;好东西要与大家分享&#xff0c;代码如下&#xff1a;此段代码放于<head> ....…

360浏览器5兼容模式吗_个独模式真能将企业总体税负降低至5%吗,为何大家都热衷核定征收...

个独模式真的能将企业总体税负降低至5%吗&#xff0c;为何大家都热衷选择申请核定征收企业性质不同&#xff0c;结构不同&#xff0c;体量不同&#xff0c;业务模式不同&#xff0c;自然纳税方式和税负也有差别。不同的纳税对象应该采取不同的节税方式&#xff0c;用合理合法的…

python3 for循环_从零开始学习PYTHON3讲义(六)for循环跟斐波那契数列

《从零开始PYTHON3》第六讲 几乎但凡接触过一点编程的人都知道for循环&#xff0c;在大多数语言的学习中&#xff0c;这也是第一个要学习的循环模式。 但是在Python中&#xff0c;我们把for循环放到了while循环的后面。原因是&#xff0c;Python中的for循环已经完全不是你知道的…