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

news/2024/5/19 3:31:04 标签: java, 设计模式, spring, ioc, 反射
  • IOC:  Inversion Of Control 控制反转

  • DI:   Dependency  Injection 依赖注入

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

1.1  IOC理论产生的背景

讨论控制反转之前,先看看软件系统提出控制反转的前世今生。
一个完整精密的软件系统,组件之间就像齿轮,协同工作,相互耦合。

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

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

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

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

1.2 IOC理论

软件专家为此提出IOC理论,用来实现对象之间的解耦。
再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:

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

  2. 软件系统在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
    通过前后对比,我们不难看出:
    对象A获得依赖对象B的过程,由主动变为了被动行为,控制权颠倒过来,这就是“控制反转”的由来。

1.3 控制反转 和 依赖注入

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

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的理解:在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!");
    }

结果如下图:从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. 控制反转是一种在软件工程中解耦合的思想,调用方依赖接口或抽象类,减少了耦合,控制权交给了服务容器,由容器维护注册项,并将具体的实现动态注入到调用方。

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

  3. 在运行时,框架会解析依赖树、依赖图,通过反射在运行期生成对象。



http://www.niftyadmin.cn/n/1864579.html

相关文章

CpuWhere的源代码和程序文件

CpuWhere的源代码和程序文件 冒着酷暑&#xff0c;这个周末将CpuWhere工具整理完毕。完整的源代码已经上传到高端调试网站&#xff0c;所有高端调试网站的注册用户都可以自由下载。链接为&#xff1a;http://advdbg.org/download/storeshow.aspx?id0232bc08-1f99-407d-bae1-0…

从 TDD 到测试策略

“ 前端没法 TDD / 前端不容易做 TDD / 前端 TDD 收益不大 ” 这是进公司后无数人给我判的“死刑”。 事实上好像的确如此&#xff1f; 在这个崇尚敏捷的组织里&#xff0c;我们有毕业生的入职前培训&#xff0c;入职后培训&#xff0c;有 TwU&#xff0c;有无数定期不定期的…

随笔,发现的技巧,有用的函数

取得domobj的实际高度 domobj.clientHeight||domobj.documentElement.clientHeight; 使用socket下载一个url,默认自动尝试三次. private function getSContent($url,$retry0,$maxRetry3){ $url_param parse_url($url); if($retry>$maxRetry){ echo "Download $url…

重新思考 React 项目架构

在开始聊之前&#xff0c;我想定义一下架构的概念。大部分小伙伴都会问一个问题&#xff1a; “ 前端能有啥架构&#xff0c;不就是文件放到哪个文件夹吗&#xff1f; ”对&#xff0c;但也不对&#xff0c;所以我们需要对齐认知&#xff1a; 到底什么是架构&#xff1f; 在开始…

吐槽一下Abp的用户和租户管理模块

1. 背景ASP.NET Core 基于声明的访问控制到底是什么鬼&#xff1f;聊到基于声明的身份认证将 身份和签发机构分离&#xff0c;应用程序信任签发机构&#xff0c;故认可签发的身份信息。-----------ClaimB站:438962688 Name:饭思思_weibo:538210234 Name:饭思思van姓名:不详 籍贯…

在IIS中部署SPA应用,多么痛的领悟!

目前公司的Web项目是SPA应用&#xff0c;采用前后端分离开发&#xff0c;所以有时也会倒腾Vue框架。“前后端应用最终以容器形态、在k8s中部署, 为此我搭建了基于Gitlab flow的Devops流程。在Devops实践中&#xff0c;容器部署成为良方和事实标准。但是在开发和自测阶段&#x…

解锁环境变量在云原生应用中的各种姿势

应用程序在某些时刻总是需要一些外挂配置&#xff0c;云原生应用的实践是在容器化之前就将应用程序配置保留在代码之外。“12-Factors App&#xff1a;Store config in the environment① 外挂配置文件&#xff1a;业务配置 appsettings.json“可以在代码中要求加载appsetting.…

一套标准的ASP.NET Core容器化应用日志收集分析方案

点击上方蓝字给一个关注吧讲故事关注我公众号的朋友&#xff0c;应该知道我写了一些云原生应用收集和分析相关的文章&#xff0c;其中内容大多聚焦某个具体的组件&#xff1a;超级有用的TraceId&#xff0c;快点用起来吧&#xff01;如何利用NLog输出结构化日志&#xff0c;并在…