控制反转、依赖注入(IOC、DI)

  • IOC:  Inversion Of Control 控制反转
  • DI:   Dependency  Injection 依赖注入

 

 

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

1.1  IOC理论产生的背景

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

  • 一个零件不正常,整个系统就崩溃了。
  • 系统对象之间耦合关系没法避免,在项目规模和复杂度变大的状况下,管理类之间的依赖关系将会很复杂。
  • 对象之间耦合度很高的系统,架构师和开发人员对于系统的修改,必然会出现牵一发而动全身的情形。
  • 对象之间耦合性依赖,单元测试很复杂。
1.2 IOC理论

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

  • 软件系统在没有引入IOC容器以前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,本身必须主动去建立对象B或者使用已经建立的对象B。不管是建立仍是使用对象B,控制权都在本身手上。
  • 软件系统在引入IOC容器以后,这种情形就彻底改变了,因为IOC容器的加入,对象A与对象B之间失去了直接联系,因此,当对象A运行到须要对象B的时候,IOC容器会主动建立一个对象B注入到对象A须要的地方。

 

 

 经过先后对比,咱们不难看出:
对象A得到依赖对象B的过程,由主动变为了被动行为,控制权颠倒过来,这就是“控制反转”的由来。sql

1.3 控制反转 和 依赖注入

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

 

 

 2 依赖注入 Dependency  Injection

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

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

2.1 ASP.NET Core依赖注入

使用方式大致相似:框架

  • 定义依赖实现的接口或者抽象类
  • 在服务容器中注册组件依赖 :IServiceProvider
  • 在构造函数中注入服务, 框架会负责建立和销毁实例
 1 // 编写组件和服务
 2 public interface IMyDependency
 3 {
 4     string WriteMessage(string message);
 5 }
 6 ---
 7 public class MyDependency : IMyDependency
 8 {
 9     public string WriteMessage(string message)
10     {
11        return $"MyDependency.WriteMessage Message: {message}";
12     }
13 }
14 // 注册组件和依赖,下面注册的`IMyDependency`在一个web请求中有效
15 public void ConfigureServices(IServiceCollection services)
16 {
17     services.AddScoped<IMyDependency, MyDependency>();
18     services.AddRazorPages();
19 }
20 ---
21 // 在构造函数注入组件
22 public class HomeController: AbpController
23 {
24     private readonly IMyDependency _dep;
25     public HomeController(IMyDependency dep)
26     {
27        _dep = dep;
28     }
29 
30   public IActionResult Index()
31   {
32     var content =  _dep.WriteMessage($"The Reflection instance is {_dep.GetType().FullName} ");
33     return Content(content);
34   }
35 }

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

上面的示例代码造成 req--->HomeController--->IMyDependency依赖树。编辑器

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

输出: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命名空间中的类能够获取可装配组件、类、接口的信息,并提供了在运行时建立实例,调用动态实例方法、获取动态实例的能力。

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

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

1    public MyDependency()
2     {
3        throw new Exception("exception content!");
4     }

结果以下图:

 

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

 1 protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
 2 {
 3     object[] parameterValues;
 4     if (constructorCallSite.ParameterCallSites.Length == 0)
 5     {
 6          parameterValues = Array.Empty<object>();
 7     }
 8     else
 9     {
10        parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
11        for (var index = 0; index < parameterValues.Length; index++)
12       {
13          parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
14        }
15     }
16 
17     try
18     {
19        return constructorCallSite.ConstructorInfo.Invoke(parameterValues);
20     }
21     catch (Exception ex) when (ex.InnerException != null)
22     {
23        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
24       // The above line will always throw, but the compiler requires we throw explicitly.
25       throw;
26      }
27 }

黄色背景行就是.NET反射特性的体现:
对类型信息(构造函数、参数)使用Invoke方法产生对象。

干货旁白

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