.NET 依赖注入中的 Captive Dependency
大家好,上一篇我们分析了 .NET 依赖注入的默认行为,其实呢还没完全讲完。今天我先给大家出一道题:
public interface IDbContext
{
}
public class SqlServerDbContext : IDbContext
{
}
public class LongTermSerive : BackgroundService
{
private readonly IDbContext _context;
public LongTermSerive(IDbContext context)
{
_context = context;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
builder.Services.AddScoped<IDbContext, SqlServerDbContext>();
builder.Services.AddHostedService<LongTermSerive>();
请问以上服务的注册有没有问题?
熟悉 .NET 的同学很快就会说:这当然有问题,IDbContext 是 Scope 生命周期,LongTermSerive 因为注册成了 HostedService 所以实际上它是 Singleton 生命周期。Singleton 不能持有 Scope 生命周期的服务。说的更通用一点的话就是:生命周期长的服务无法依赖生命周期比它的服务。
真的是这样吗???
以上回答只说对了一半。这时候肯定马上会有同学跳出来说,“这怎么会不对呢?我刚刚都试过了,VS直接报错了”。
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: DevelopmentTest.LongTermSerive': Cannot consume scoped service 'DevelopmentTest.IDbContext' from singleton
不要着急让我们继续分析下去。
Captive Dependency
首先让我们澄清一个概念。像以上这种情况:当生命周期长(Singleton)的服务持有生命周期短(Scope)的服务的时候我们叫做 "Captive Dependency"(Transient不在讨论范围内)。
不知道怎么翻译成中文比较合适。微软的文档上翻译作"捕获依赖",个人认为不太恰当。
"Captive Dependency" 会带来什么问题?
- 生命周期短的服务会被 DI 容器及时释放,比如调用了 Dispose 方法,导致后续的操作失败。
- 非线程安全。Singleton 的对象很容易被多个线程共享,但 Scope 的话大多数情况都是非线程安全的。比如上面的 DbContext,当在线程内共享,发生并发操作的时候程序是无法保证正确运行的。
.NET DI 支持 Captive Dependency 吗?
当我们了解这个概念后,上面的问题可以转换成 " .NET DI 支持 Captive Dependency 吗?"。
根据上一次我们的文章的内容,我们知道 .NET DI 的行为是跟所在的环境有关系的。所以讨论这个问题我们还是要分开来看待:
- Development 环境下,.NET DI 会在构建 ServiceProvider 的时候去校验服务的依赖关系。这个时候就会像上面提到的一样,直接报错。
- 非 Development 环境下在构建 ServiceProvider 的时候不会校验服务间的依赖关系,程序有可能正确运行。为什么说是有可能呢?因为这个完全取决与你的代码是怎么写的。也许你短生命周期的服务在某些场景下正巧可以工作,又或者正巧不能工作。但是有一点是明确的,就是 Captive Dependency 是危险的。因为当你注册成 Scope 或者 Transient 的时候往往是带了某种暗示。比如 Scope 对象是非线程安全的。显然 Socpe 服务的编写者没有义务去考虑被 Singleton 服务依赖时候的问题。
- 手动开启
ValidateScopes = true的时候不管什么环境下都会进行依赖关系的校验,类似 Development 环境下。
总结
现在我们可以作一个总结:
.NET DI 是支持 Captive Dependency 的,但是在 Development 环境下或者手动开启 ValidateScopes = true 的时候它不支持,它会阻止 Captive Dependency。换句话说 .NET DI 在阻止 Captive Dependency 上只做了一半的工作,并不能 100% 确保不发生 Captive Dependency。开发者们在写代码的时候还是要自己注意了,不能完全依赖 .NET 的检测。
关于这个问题,我也在 .NET Runtime 的 Repository 下开了一个 ticket 进行讨论。微软给出的理由是基于性能的考虑,生产环境这个校验默认不开启。其实我个人觉得微软应该不管在什么环境下都默认开启校验,尽可能的避免 Captive Dependency。因为 90% 的项目其实并不在乎这点性能开销。如果你的应用程序真的很在乎性能那么可以手动关闭这个校验,这个时候开发者自己需要完全对这个依赖关系负责。
https://blog.ploeh.dk/2014/06/02/captive-dependency/
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#captive-dependency
https://github.com/dotnet/runtime/discussions/109491
.NET 依赖注入中的 Captive Dependency的更多相关文章
- .Net Core依赖注入中TryAddEnumerable 和TryAddTransient方法的区别
.Net Core依赖注入添加的每个服务,最终都会转换为一个ServiceDescriptor的实例,ServiceDescriptor包含以下属性: Lifetime:服务的生命周期(Singlet ...
- spring依赖注入中接口的问题
问题描述:一个接口,有俩个实现类当注入时候名字不同时,会出现不同的情况 action层: @Controller("userAction") @Scope("protot ...
- Spring 依赖注入中 Field 注入的有害性
大致分为:Field 注入.构造注入.setter 注入 其中 Field 注入被认为有害的: 1. 违反了单一原则 当一个 class 中有多个依赖时,如果仅仅使用 Field 注入,则看不出有很多 ...
- spring依赖注入中获取JavaBean
一.这个接口有什么用? 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean.换句话说,就是这个类可以 ...
- Spring点滴七:Spring中依赖注入(Dependency Injection:DI)
Spring机制中主要有两种依赖注入:Constructor-based Dependency Injection(基于构造方法依赖注入) 和 Setter-based Dependency Inje ...
- 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- 在.NET Core控制台程序中使用依赖注入
之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core 中的依赖注入 [共7篇]
一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...
- NET Core 中的依赖注入
NET Core 中的依赖注入 [共7篇] 一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制, ...
随机推荐
- 本文是第一篇在GitHub仓库中撰写的.md格式的blog文件
正文内容: 具体内容,只是未来测试,给出福利: 模板格式: title: 博文标题 description: 博文摘要 #多个标签请使用英文逗号分隔或使用数组语法 tags: 标签1, 标签2 #多个 ...
- java api 接口 postman @RequestBody
首先解释一下注解 @RequestBody @RequestBody标记的参数接受的是一个json格式的字符串,一定是一个字符串. 举个例子 这里参数 entryingEntity 如果调用api 如 ...
- Help document of CAD Plus
中文使用帮助 Help for Mobile Update time: 2023-07-29; This article will help you how to use the CAD Plus a ...
- Python如何获取request response body
在Python中,我们可以使用多个库来发送HTTP请求并获取响应体(response body).其中,最常用的库之一是 requests.这个库提供了简单易用的接口来发送HTTP请求,并可以方便地获 ...
- php子孙树生成层级-递归篇
毫不废话,代码如下: <?php $list = [ ['id' => 1, 'pid' => 0, 'name' => '江苏'], ['id' => 2, 'pid' ...
- linux 自动输入密码脚本避免密码确认
有时候需要执行一个小脚本,就把一部分命令集合起来,我们可以使用 && 或者 .sh 脚本换行. 而有些时候涉及权限需要输入密码就出现了一些客户端会卡在输密码的界面让用户输入 脚本会暂停 ...
- 淘宝长仁:JVM性能指标的理论极限和衡量方法(TaobaoJVM)
在2013年阿里巴巴集团主办的ADC•阿里技术嘉年华,这是一场专属于<互联网工程师>的"技术盛宴",倡导<干货分享>的大会上,51CTO记者有幸采访到了阿里 ...
- 创建一个具有商业品质的 Eclipse IDE
创建具有商业品质且可插入 Eclipse 的专业 IDE Prashant Deva (pdeva@placidsystems.com), 创始人, Placid Systems 简介: " ...
- 站点监控工具之uptime-kuma
github: https://github.com/louislam/uptime-kuma [安装] docker: docker run -d --restart=always -p 3001: ...
- Vue3 组合式API
1.入口 创建实例时,配置setup方法,然后其内部书写组合式API代码,通过组合式API生产的数据和返回,需要暴漏出去才能给HTML使用 <script> //组合式(解构赋值) con ...