小王的故事

小王去上班

​ 小王是个程序员,每个工作日他都要去上班,诸多交通工具他最喜欢的交通工具是骑电车。在骑行的过程中放空自己使他很快。

​ 突然有一天天气预报说近期有很大的雨,小王再想骑电车去上班显然是不可能了。那么他就要选择开汽车去。

但是由于小王每天过于依赖骑电动车,开汽车水平有限,那么他就要重新学习开汽车。

​ 因此小王很烦恼,我就想去上个班,还要掌握不同的交通工具,这真是让人烦恼,难道我就想做一个单纯的打工族就这么难吗?小王就把这件事告诉了他的老婆,她老婆是个老司机,她说“这事你就不用管了,我是老司机啊,开车这事我来控制就好了,你就记得给我多买些礼物就好“。从此之后小王就安心的为了老婆的赚钱,开车这事就完全有他老婆负责。

小王的日记:

​ 我曾经很享受自己去上班(自己控制),每天去上班就拿出我心爱的电动车(实例化对象),直到有一天天气预报告诉我要下大雨,再依赖自行车就会被大雨淋着,而换交通工具我发现我就要重新学习开车(高耦合),知道我老婆大人说她是老司机(ioc容器),开车这事由她来控制(控制反转),不管怎么去上班,事先告诉他用什么交通工具就行(依赖注入),从此我每个工作日只要叫上我老婆她就直接带我去上班了。


依赖注入

​ 从小王的故事我们可以看到一些关键词依赖控制 而今天要说的就是什么是依赖注入。说到依赖注入(DI)还有个概念叫控制反转(IOC)。

控制反转(IOC—Inversion of Control)不是什么技术,而是一种设计思想。它的思路是设计好的依赖类交给容器控制,而不是在对象中直接实例化控制。就是在系统运行时ioc容器动态向一个对象提供他依赖的其他对象。而他的实现就是用依赖注入来实现。

依赖注入(DI—Dependency Injection)是组件之间依赖关系由容器决定。为此我们要明白依赖和注入关系

依赖:由于应用程序需要ioc容器提供对象外部的资源所以应用程序依赖于ioc容器

注入:某个对象所需要的外部资源(包括对象、资源、常量数据)注入到了ioc容器中。


net5 内置依赖注入

依赖注入是net core和net5的核心概念,我们在了解完概念之后也要对我们框架中对他的实现有个清楚的认识。当然官方文档是最好的了解方式。接下来我从我的角度去说一下对net 5(net core)依赖注入的理解.首先我们要知道net5依赖注入的实现由net 内置的容器和第三方的容器。我们主要说内置的实现情况。

内置依赖注入

Microsoft.Extensions.DependencyInjection

net5 内置依赖注入主要用到了Microsoft.Extensions.DependencyInjectionMicrosoft.Extensions.DependencyInjection.Abstraction两个引用。我们nuget引用就行。它们是开源的。



我们可以看到DependencyInjection这个项目并不大,但却是整个net5(NET Core)的基础,因为它提供了依赖注入容器的默认实现,而依赖注入是net5(net core)的核心基础.

源码解析

IServiceCollection

public class ServiceCollection : IServiceCollection
{
//ServiceDescriptor缓存集合
private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
//注册到当前ServiceCollection对象中的ServiceDescriptor数量
public int Count => _descriptors.Count;
public bool IsReadOnly => false;
public ServiceDescriptor this[int index]
{
get
{
return _descriptors[index];
}
set
{
_descriptors[index] = value;
}
}
//清空所有ServiceDescriptor对象
public void Clear()
{
_descriptors.Clear();
}
//查询ServiceCollection是否包含指定ServiceDescriptor对象
public bool Contains(ServiceDescriptor item)=> _descriptors.Contains(item);
//拷贝ServiceDescriptor
public void CopyTo(ServiceDescriptor[] array, int arrayIndex) =>_descriptors.CopyTo(array, arrayIndex);
//从ServiceCollection移除指定ServiceDescriptor
public bool Remove(ServiceDescriptor item)=>_descriptors.Remove(item);
//获取此ServiceCollection的迭代器
public IEnumerator<ServiceDescriptor> GetEnumerator()=> _descriptors.GetEnumerator();
public int IndexOf(ServiceDescriptor item) => _descriptors.IndexOf(item);
public void Insert(int index, ServiceDescriptor item) => _descriptors.Insert(index, item);
public void RemoveAt(int index)=> _descriptors.RemoveAt(index);
}

IServiceCollection&&ServiceDescriptor

namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// 指定服务描述符集合的约定
/// </summary>
public interface IServiceCollection : IList<ServiceDescriptor>
{
}
}

我们从代码中可以看到内置依赖注入,它的内置容器是ServiceProvider,我们会把我们的服务注入到ServiceProvider 中来,而IServiceCollection是ServiceProvider的list的集合。

ServiceDescriptor

     public static IServiceCollection Add(this IServiceCollection collection,IEnumerable<ServiceDescriptor> descriptors)
{
if (collection == null)
{
throw new ArgumentNullException(nameof(collection));
}
if (descriptors == null)
{
throw new ArgumentNullException(nameof(descriptors));
}
foreach (var descriptor in descriptors)
{
collection.Add(descriptor);
}
return collection;
}

我们可以看到服务注册的时候,提供了ServiceDescriptor

 /// <summary>
/// Describes a service with its service type, implementation, and lifetime.
/// </summary>
[DebuggerDisplay("Lifetime = {Lifetime}, ServiceType = {ServiceType}, ImplementationType = {ImplementationType}")]
public class ServiceDescriptor
{}

它是对服务的类型、生命周期、获取服务的方式的描述。



类型

//注册的类型的生命周期
public ServiceLifetime Lifetime { get; }
//基类型
public Type ServiceType { get; }
//实例类型(派生类型)
public Type ImplementationType { get; }
//实例对象
public object ImplementationInstance { get; }
//注册类型实例化对象的工厂
public Func<IServiceProvider, object> ImplementationFactory { get; }

构造函数

//派生类型
public ServiceDescriptor(Type serviceType,object instance)
: this(serviceType, ServiceLifetime.Singleton)
{
Lifetime = lifetime;
ServiceType = serviceType;
ImplementationInstance = instance;
}
//工厂
public ServiceDescriptor(Type serviceType,Func<IServiceProvider, object> factory,ServiceLifetime lifetime)
: this(serviceType, lifetime)
{
Lifetime = lifetime;
ServiceType = serviceType;
ImplementationFactory = factory;
}
//具体实例对象
public ServiceDescriptor(Type serviceType,Type implementationType,ServiceLifetime lifetime)
: this(serviceType, lifetime)
{
Lifetime = lifetime;
ServiceType = serviceType;
ImplementationType = implementationType;
}
/// <summary>
///获取当前注册类型的实例类型(内部类)
/// </summary>
/// <returns></returns>
internal Type GetImplementationType(){}
//真正实例化对象的方法,重载都是调用此类方法
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime){}
public static ServiceDescriptor Singleton(Type serviceType,object implementationInstance){}
public static ServiceDescriptor Scoped(Type service, Func<IServiceProvider, object> implementationFactory){}

ServiceCollectionServiceExtensions&ServiceCollectionDescriptorExtensions&ServiceCollectionContainerBuilderExtensions

这三个方法时ServiceCollection的三个扩展方法分别实现:我们所使用了注册方式;TryAdd和RemoveAll,Replace等操作和构造ServiceProvider实例。

ServiceCollectionServiceExtensions:

代码太多(不同生命周期的注册)就不贴出了,截个图算了。



ServiceCollectionDescriptorExtensions:

![image-20210515122419417](https://gitee.com/wyl1924/cdn/raw/master/img/blog/image-20210515122419417.png

public static void TryAdd(this IServiceCollection collection,ServiceDescriptor descriptor)
{
if (!collection.Any(d => d.ServiceType == descriptor.ServiceType))
collection.Add(descriptor);
}

注:Add,RemoveAll,Replace,没什么好说的,其中TryAdd,TryAddEnumerable需要注意到的是:中有服务集合中不存在才会进行过注册。

ServiceCollectionContainerBuilderExtensions

    public static class ServiceCollectionContainerBuilderExtensions
{
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) }

IServiceProvider

public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback
{
// ServiceProvider的扩展接口
// 使用这个接口的子类进行调用缓存各种注册服务和调用访问者对象进行获取实例对象
private readonly IServiceProviderEngine _engine;
/// 缓存类型(访问者模式)
private readonly CallSiteValidator _callSiteValidator;
internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
IServiceProviderEngineCallback callback = null;
if (options.ValidateScopes)
{
callback = this;
_callSiteValidator = new CallSiteValidator();
}
//实例化工作引擎类型
switch (options.Mode)
{
case ServiceProviderMode.Dynamic:
// 实例化 DynamicServiceProviderEngine
_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
break;
case ServiceProviderMode.Runtime:
_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
break;
case ServiceProviderMode.ILEmit:
_engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);
break;
case ServiceProviderMode.Expressions:
_engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);
break;
default:
throw new NotSupportedException(nameof(options.Mode));
}
}
// 获取指定类型的服务对象
public object GetService(Type serviceType) => _engine.GetService(serviceType);
public void Dispose() => _engine.Dispose();
//创建服务实例缓存
void IServiceProviderEngineCallback.OnCreate(ServiceCallSite callSite)
=>_callSiteValidator.ValidateCallSite(callSite);
//服务实例的校验
void IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)
=>_callSiteValidator.ValidateResolution(serviceType, scope, _engine.RootScope);
}

我们可以看到ServiceProvider用来获取服务的实例,它提供了一个扩展IServiceProviderEngine来实现其功能。x下面我们来看一下源码

public static class ServiceProviderServiceExtensions
{
// 从IServiceProvider获取“T”类型的服务。
public static T GetService<T>(this IServiceProvider provider)
=> (T)provider.GetService(typeof(T)); // 从IServiceProvider获取“T”类型的服务。
public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
{
// 如果当前ServiceProvider实现了 ISupportRequiredService
// 则直接调用当前ServiceProvier的GetRequiredService获取服务实例
var requiredServiceSupportingProvider = provider as ISupportRequiredService;
if (requiredServiceSupportingProvider != null)
return requiredServiceSupportingProvider.GetRequiredService(serviceType);
//如果当前ServiceProvider未实现ISupportRequiredService
//就直接调用GetService获取服务实例,但是如果服务实例为空,则抛出异常
var service = provider.GetService(serviceType);
if (service == null)
throw new InvalidOperationException(Resources.FormatNoServiceRegistered(serviceType));
return service;
}
public static T GetRequiredService<T>(this IServiceProvider provider)
=> (T)provider.GetRequiredService(typeof(T));
//获取指定注册类型<T>的所有服务实例
public static IEnumerable<T> GetServices<T>(this IServiceProvider provider)
=> provider.GetRequiredService<IEnumerable<T>>();
//获取指定注册类型<T>的所有服务实例
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType)
{
//制造一个serviceType类型的IEnumberable<>集合,serviceTypele类型作为当前集合的泛型参数
var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
return (IEnumerable<object>)provider.GetRequiredService(genericEnumerable);
} //创建一个子IServiceProvider(容器)实例
public static IServiceScope CreateScope(this IServiceProvider provider)
=> provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

NET 内置依赖注入实现

我们再Startup.ConfigureServices(IServiceCollection services)进行注册下面我直接用代码来看一下实现

public class UserService : IUserService
{
public string GetName()
{
return "王延领";
}
}
public interface IUserService
{
string GetName();
}

1.面向接口注册

services.AddScoped(typeof(IUserService), typeof(UserService));
services.AddScoped<IUserService, UserService>();

2.实现形式注册

services.AddScoped<UserService>();
services.AddScoped(typeof(UserService));

两种形式都可以,但扩展性显然是第一种好。

3.批量注入

var assembly = Assembly.GetExecutingAssembly()
.DefinedTypes
.Where(a => a.Name.EndsWith("Service") && !a.Name.StartsWith("I"));
foreach (var item in assembly)
{
services.AddScoped(item.GetInterfaces().FirstOrDefault(), item);
}

第三方容器(Autofact)注入

后续单独一篇文章再写吧。

net5学习笔记---依赖注入的更多相关文章

  1. Spring 学习笔记 ----依赖注入

    依赖注入 有三种方式,本文只学习下属性注入. 属性注入       属性注入即通过 setXxx方法()注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入方式是 ...

  2. Spring学习笔记--依赖注入

    依赖注入和控制反转:http://baitai.iteye.com/blog/792980出自李刚<轻量级 Java EE 企业应用实战> Java应用是一种典型的依赖型应用,它就是由一些 ...

  3. DotNETCore 学习笔记 依赖注入和多环境

    Dependency Injection ------------------------------------------------------------------------ ASP.NE ...

  4. ADO学习笔记之注入漏洞与参数化查询

    ADO学习笔记之注入漏洞与参数化查询 作为新手,在学习ADO程序时,使用 sql 语言查询数据时,很容易写类似如下代码: using (SqlConnection con = new SqlConne ...

  5. Web安全学习笔记 SQL注入下

    Web安全学习笔记 SQL注入下 繁枝插云欣 --ICML8 SQL注入小技巧 CheatSheet 预编译 参考文章 一点心得 一.SQL注入小技巧 1. 宽字节注入 一般程序员用gbk编码做开发的 ...

  6. Web安全学习笔记 SQL注入上

    Web安全学习笔记 SQL注入上 繁枝插云欣 --ICML8 SQL注入分类 SQL注入检测 一.注入分类 1.简介 SQL注入是一种代码注入技术用于攻击数据驱动的应用程序在应用程序中,如果没有做恰当 ...

  7. Web安全学习笔记 SQL注入中

    Web安全学习笔记 SQL注入中 繁枝插云欣 --ICML8 权限提升 数据库检测 绕过技巧 一.权限提升 1. UDF提权 UDF User Defined Function,用户自定义函数 是My ...

  8. 学习Spring——依赖注入

    前言: 又开始动笔开了“学习Spring”系列的头…… 其实一开始写“学习SpringMVC”的几篇文章是出于想系统的了解下Spring以及SpringMVC,因为平时在公司中虽然每天都在使用Spri ...

  9. Spring框架学习之依赖注入

    Spring框架从2004年发布的第一个版本以来,如今已经迭代到5.x,逐渐成为JavaEE开发中必不可少的框架之一,也有人称它为Java下的第一开源平台.单从Spring的本身来说,它贯穿着整个表现 ...

随机推荐

  1. 在CentOS上安装Nginx配置HTTPS并设置系统服务和开机启动(最全教程)

    友情提示:全部配完大约需要20分钟,本教程配合 xshell 和 xftp 使用更佳. 系统配置:CentOS 7.5 本教程 摘繁华 版权所有. 操作按键 常用按键: 复制操作:Shift+Ins ...

  2. lucent,solr,ES比较

    |0什么是全文搜索 什么是全文搜索引擎? 百度百科中的定义:全文搜索引擎是目前广泛应用的主流搜索引擎.它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现 ...

  3. 《数据持久化与鸿蒙的分布式数据管理能力》直播课答疑和PPT分享

    问:hi3861开发板支持分布式数据库吗? 目前,分布式数据库仅支持Java接口,因此Hi3861没有现成的API用于操作分布式数据库. 问:分布式数据管理包括搜索吗? 分布式数据管理包括融合搜索能力 ...

  4. Stone Game, Why are you always there? HDU - 2999

    题目链接:https://vjudge.net/problem/HDU-2999 题意:有N堆石头,两个人交替取,每次只能取连续的k个石子,最后没有石子取得人输. 思路:如果我们每次取靠边的k个,那么 ...

  5. JAVA面试-计算机网络-TCP三次握手

    学习原因 这个是面试的一个常问热点,所以务必要掌握. 通俗示例 小红是人事部门的员工,现在正在招收IT人员,小明看到招聘信息和待遇,感觉很适合自己,所以准备和小红发消息了解具体情况.而简历在本故事中代 ...

  6. [题解] T'ill It's Over

    前言 线段树+网络最大流的建模题. 博客园食用更佳 题目大意 最初时有 \(n\) 个 \(1\) .给定 \(op\) . \(l\) ,其中, \(l\) 为操作次数上限.你有四个操作: 若 \( ...

  7. 多任务学习(MTL)在转化率预估上的应用

    今天主要和大家聊聊多任务学习在转化率预估上的应用. 多任务学习(Multi-task learning,MTL)是机器学习中的一个重要领域,其目标是利用多个学习任务中所包含的有用信息来帮助每个任务学习 ...

  8. Linux 网络工具中的瑞士军刀 - socat & netcat

    独立博客阅读:https://ryan4yin.space/posts/socat-netcat/ 文中的命令均在 macOS Big Sur 和 Opensuse Tumbleweed 上测试通过 ...

  9. 前端面试回顾(1)---javascript的面向对象

    前言 前一阵面试,过程中发现问到一些很基础的问题时候,自己并不能很流畅的回答出来.或者遇到一些基础知识的应用,由于对这些点理解的不是很深入,拿着笔居然什么都写不出来,于是有了回顾一下这些基础知识的想法 ...

  10. shell脚本 3 流程控制

    shell流程控制 流程控制是改变程序运行顺序的指令.linux shell有一套自己的流程控制语句,其中包括条件语句(if),循环语句(for,while),选择语句(case).下面我将通过例子介 ...