从EFCore上下文的使用到深入剖析DI的生命周期最后(2)

为什么危险?到底什么是root provider?那就要从原生DI的生命周期说起。我们知道,DI容器被封装成一个IServiceProvider对象,服务都是从这里来获取。不过这并不是一个单一对象,它是具有层级结构的,最顶层的即前面提到的root provider,可以理解为仅属于系统层面的DI控制中心。在Asp.Net Core中,内置的DI有3种服务模式,分别是Singleton、Transient、Scoped,Singleton服务实例是保存在root provider中的,所以它才能做到全局单例。相对应的Scoped,是保存在某一个provider中的,它能保证在这个provider中是单例的,而Transient服务则是随时需要随时创建,用完就丢弃。由此可知,除非是在root provider中获取一个单例服务,否则必须要指定一个服务范围(Scope),这个验证是通过ServiceProviderOptions的ValidateScopes来控制的。默认情况下,Asp.Net Core框架在创建HostBuilder的时候会判定当前是否开发环境,在开发环境下会开启这个验证:

所以前面那种关闭验证的方式是错误的。这是因为,root provider只有一个,如果恰好有某个singleton服务引用了一个scope服务,这会导致这个scope服务也变成singleton,仔细看一下注册DbContext的扩展方法,它实际上提供的是scope服务:

如果发生这种情况,数据库连接会一直得不到释放,至于有什么后果大家应该都明白。

所以前面的测试代码应该这样写:

using (var serviceScope = app.ApplicationServices.CreateScope()) { var context = serviceScope.ServiceProvider.GetService<BloggingContext>(); }

与之相关的还有一个ValidateOnBuild属性,也就是说在构建IServiceProvider的时候就会做验证,从源码中也能体现出来:

if (options.ValidateOnBuild) { List<Exception> exceptions = null; foreach (var serviceDescriptor in serviceDescriptors) { try { _engine.ValidateService(serviceDescriptor); } catch (Exception e) { exceptions = exceptions ?? new List<Exception>(); exceptions.Add(e); } } if (exceptions != null) { throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray()); } }

正因为如此,Asp.Net Core在设计的时候为每个请求创建独立的Scope,这个Scope的provider被封装在HttpContext.RequestServices中。

[小插曲]

通过代码提示可以看到,IServiceProvider提供了2种获取service的方式:

从EFCore上下文的使用到深入剖析DI的生命周期最后

这2个有什么区别呢?分别查看各自的方法摘要可以看到,通过GetService获取一个没有注册的服务时会返回null,而GetRequiredService会抛出一个InvalidOperationException,仅此而已。

// 返回结果: // A service object of type T or null if there is no such service. public static T GetService<T>(this IServiceProvider provider); // 返回结果: // A service object of type T. // // 异常: // T:System.InvalidOperationException: // There is no service of type T. public static T GetRequiredService<T>(this IServiceProvider provider);

终极大招

到现在为止,尽管找到了一种看起来合理的方案,但还是不够优雅,使用过其他第三方DI框架的朋友应该知道,属性注入的快感无可比拟。那原生DI有没有实现这个功能呢,我满心欢喜上G站搜Issue,看到这样一个回复(https://github.com/aspnet/Extensions/issues/2406):

从EFCore上下文的使用到深入剖析DI的生命周期最后

官方明确表示没有开发属性注入的计划,没办法,只能靠自己了。

我的思路大概是:创建一个自定义标签(Attribute),用来给需要注入的属性打标签,然后写一个服务激活类,用来解析给定实例需要注入的属性并赋值,在某个类型被创建实例的时候也就是构造函数中调用这个激活方法实现属性注入。这里有个核心点要注意的是,从DI容器获取实例的时候一定要保证是和当前请求是同一个Scope,也就是说,必须要从当前的HttpContext中拿到这个IServiceProvider。

先创建一个自定义标签:

[AttributeUsage(AttributeTargets.Property)] public class AutowiredAttribute : Attribute { }

解析属性的方法:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdsyfg.html