学完这篇依赖注入,与面试官扯皮就没有问题了。

简介: 控制反转是一种在软件工程中解耦合的思想,调用方依赖接口或抽象类,减少了耦合,控制权交给了服务容器,由容器维护注册项,并将具体的实现动态注入到调用方。有些人会把控制反转和依赖注入等同,实际上有本质区别:控制反转是一种思想;依赖注入是一种设计模式。依赖注入是实现控制反转的一种方式,但是控制反转还有其他实现方式,例如说ServiceLocator,所以不能将控制反转和依赖注入等同。在运行时,框架会解析依赖树、依赖图,通过反射在运行期生成对象。阅读更多

9529294a95b64c00b0e631eb61cdaea8.png


1.控制反转 Inversion Of Control 的前世今生


1.1  IOC理论产生的背景


讨论控制反转之前,先看看软件系统提出控制反转的前世今生。


一个完整精密的软件系统,组件之间就像齿轮,协同工作,相互耦合。


  • 一个零件不正常,整个系统就崩溃了。


  • 系统对象之间耦合关系无法避免,在项目规模和复杂度变大的情况下,管理类之间的依赖关系将会很复杂。


  • 对象之间耦合度很高的系统,架构师和开发人员对于系统的修改,必然会出现牵一发而动全身的情形。


  • 对象之间耦合性依赖,单元测试很复杂。


1.2 IOC理论


软件专家为此提出IOC理论,用来实现对象之间的解耦。


再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:


  1. 软件系统在没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。


  1. 软件系统在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。


481fbf81f08363a363ddb9ae69647f50.jpg


  1. 通过前后对比,我们不难看出:对象A获得依赖对象B的过程,由主动变为了被动行为,控制权颠倒过来,这就是“控制反转”的由来


1.3 控制反转 和 依赖注入


有些人会把控制反转和依赖注入等同,实际上有本质区别:


控制反转是一种思想;依赖注入是一种设计模式。


依赖注入是实现控制反转的一种方式,但是控制反转还有其他实现方式,例如说
ServiceLocator(服务定位器、依赖查找),所以不能将控制反转和依赖注入等同。


a0b373d8fef2a3503240737ddf1bac0b.png


2 依赖注入 Dependency  Injection


依赖注入:容器全权负责组件的装配,它会把符合依赖关系的对象通过属性或者构造函数传递给需要的对象。


符合依赖倒置原则,高层模块不应该依赖低层模块,两者都应该依赖其抽象


2.1 ASP.NET Core依赖注入


使用方式大体类似:


①. 定义依赖实现的接口或者抽象类


②. 在服务容器中注册组件依赖 :
IServiceProvider


③. 在构造函数中注入服务, 框架会负责创建和销毁实例


// 编写组件和服务
public interface IMyDependency
{
    string WriteMessage(string message);
}
---
public class MyDependency : IMyDependency
{
    public string WriteMessage(string message)
    {
       return $"MyDependency.WriteMessage Message: {message}";
    }
}
// 注册组件和依赖,下面注册的`IMyDependency`在一个web请求中有效
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();
    services.AddRazorPages();
}
---
// 在构造函数注入组件
public class HomeController: AbpController
{
    private readonly IMyDependency _dep;
    public HomeController(IMyDependency dep)
    {
       _dep = dep;
    }
  public IActionResult Index()
  {
    var content =  _dep.WriteMessage($"The Reflection instance is {_dep.GetType().FullName} ");
    return Content(content);
  }
}


在请求某个服务时,框架会完整解析出这个对象的依赖树和作用范围。


上面的示例代码形成 req--->HomeController--->IMyDependency依赖树。


IMyDependency在每个web请求范围内使用同一服务实例。


输出:MyDependency.WriteMessage Message: The Reflection instance is TestDI.MyDependency


2.2 对象生命周期


根据现实需要,前人从使用场景中总结出三种服务生命周期。
ASP.NET Core提供了一个枚举
ServiceLifetime


-- --- --- ---
Singleton 单例 服务容器首次请求会创建,后续都使用同一实例 AddSingleton
Scoped 特定范围 在一个请求(连接)周期内使用一个示例 AddScoped
Transient 瞬时 服务容器每次请求,都会创建一个实例 AddTransient


对于Scoped Service的理解:


470d2adfb3e2aa1205e893a1c2431d9e.png


在webapp:scoped service 会在请求结束时被销毁;


在EFCore:使用AddDbContext默认注册的是特定范围的DbContext,这意味在我们可以在一次sql连接内,使用同一个DbContext实例进行多次DB操作


2.3 依赖注入实现原理


结合理论、使用方式 猜测依赖注入的原理:


实现DI,核心在于依赖注入容器
IContainer,该容器具有以下功能


①.(容器)保存可用服务的集合
//  要用的特定对象、特定类、接口服务


②.(注册)提供一种方式将各种部件与他们依赖的服务绑定到一起;
//  Add...函数或containerBuilder.Register函数


③.(解析点)为应用程序提供一种方式来请求已配置的对象:构造函数注入、属性注入.

运行时,框架会一层层通过反射构造实例,最终得到完整对象。


3.源码导航


利用反射产生对象是依赖注入的核心过程,这也是面试造航母时经常问到的。


.NETSystem.ReflectionSystem.Type命名空间中的类可以获取可装配组件、类、接口的信息,并提供了在运行时创建实例,调用动态实例方法、获取动态实例的能力。


当我尝试从github源码中探究[依赖注入产生对象]的伪代码时,文件/代码众多,迷路了!


实际上,我们可以在依赖树的尾部对象的构造函数手动抛出异常,异常的调用栈就是一个天然的源码导航。


于是我在上面示例代码的request----> HomeController--->MyDependency MyDependency构造函数中添加异常代码:


public MyDependency()
    {
       throw new Exception("exception content!");
    }

结果如下图:


a1a369be6c696582eb40a1969d4d7eb0.png


从Github Dependency Injection 库进入System.Reflection的调用分界线代码:


protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
{
    object[] parameterValues;
    if (constructorCallSite.ParameterCallSites.Length == 0)
    {
         parameterValues = Array.Empty<object>();
    }
    else
    {
       parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
       for (var index = 0; index < parameterValues.Length; index++)
      {
         parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
       }
    }
    try
    {
       return constructorCallSite.ConstructorInfo.Invoke(parameterValues);
    }
    catch (Exception ex) when (ex.InnerException != null)
    {
       ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
      // The above line will always throw, but the compiler requires we throw explicitly.
      throw;
     }
}


黄色背景行就是.NET反射特性的体现:


对类型信息(构造函数、参数)使用Invoke方法产生对象。


干货旁白


  1. 控制反转是一种在软件工程中解耦合的思想,调用方依赖接口或抽象类,减少了耦合,控制权交给了服务容器,由容器维护注册项,并将具体的实现动态注入到调用方。


  1. 有些人会把控制反转和依赖注入等同,实际上有本质区别:
    控制反转是一种思想;
    依赖注入是一种设计模式。
    依赖注入是实现控制反转的一种方式,但是控制反转还有其他实现方式,例如说ServiceLocator,所以不能将控制反转和依赖注入等同。


  1. 在运行时,框架会解析依赖树、依赖图,通过反射在运行期生成对象。
相关文章
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
604 3
|
8月前
|
数据安全/隐私保护 UED
2025 年 Adobe Acrobat Reader、万兴 PDF 阅读器、Sumatra PDF 三款高性价比 PDF 阅读器推荐
本文介绍了三款PDF阅读器:`Adobe Acrobat Reader DC`、`万兴PDF阅读器`和`Sumatra PDF`。其中,`Adobe Acrobat Reader DC`功能全面,支持签名、批注、表单处理等高阶操作,适合专业需求;`万兴PDF阅读器`不仅阅读便捷,还提供强大的格式转换与编辑功能,支持批量操作;`Sumatra PDF`则以轻量级和快速打开著称,适合简单阅读需求。根据个人需求选择合适的工具,下载链接已提供。
806 0
|
10月前
|
开发工具 git C++
利用Cmake展示Git内容的方法
总的来说,CMake和Git是强大的工具,通过合理的使用,可以极大地提高开发效率。
240 24
|
10月前
|
Ubuntu 开发工具 C语言
Ubuntu环境下的Samba源码编译
以上就是在Ubuntu环境下编译Samba源码的步骤。希望这个指南能帮助你成功地从源码编译Samba。如果你在编译过程中遇到任何问题,你可以查阅Samba的官方文档,或者在网上搜索相关的教程和解决方案。
342 23
|
11月前
|
数据安全/隐私保护 UED 异构计算
【大模型私有化部署要花多少钱?】一张图看懂你的钱用在哪
本文探讨了高性价比实现DeepSeek大模型私有化部署的方法,分为两部分: 一是定义大模型性能指标,包括系统级(吞吐量、并发数)与用户体验级(首token生成时间、单token生成时间)指标,并通过roofline模型分析性能瓶颈; 二是评估私有化部署成本,对比不同硬件(如H20和4090)及模型选择,结合业务需求优化资源配置。适合关注数据安全与成本效益的企业参考。
【大模型私有化部署要花多少钱?】一张图看懂你的钱用在哪
|
11月前
|
人工智能 数据可视化 定位技术
AI 小技巧 | PPT 也能用数据地图?
AI 小技巧 | PPT 也能用数据地图?
571 4
|
11月前
|
人工智能 前端开发 算法
AI程序员全面上线!10分钟就能完成整个开发过程!
AI程序员全面上线!10分钟就能完成整个开发过程!
|
SQL XML Java
简单理解springboot的依赖注入
依赖注入,Dependency Injection,简称DI,是spring中的核心技术,此技术贯穿Spring全局,是必须要熟练掌握的知识点。在本文中,我们将要深入研究spring中的IOC和DI,理解核心思想,并学会如何在spring boot中使用基于java和注解的方式正确使用DI来创建spring应用程序。控制反转 IOC要理解DI,首先需要理解spring的核心思想之一,控制反转(In
2990 0
简单理解springboot的依赖注入
|
机器学习/深度学习 自然语言处理 Serverless
介绍一下什么是兼容性函数
介绍一下什么是兼容性函数
322 0
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
1103 0

热门文章

最新文章