依赖注入(DI)

依赖注入(DI)

IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用“好莱坞原则”是应用程序以被动的方式实现对流程的定制。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在上面介绍的模板方法、工厂方法和抽象工厂,接下来我们介绍一种更为有价值的IoC模式,即依赖注入(DI:Dependency Injection,以下简称DI)。

一、由外部容器提供对象

和上面介绍的工厂方法和抽象工厂模式一样,DI旨在实现针对服务对象的动态提供。具体来说,服务的消费者利用一个独立的容器(Container)来获取所需的服务对象,容器自身在提供服务对象的过程中会自动完成依赖的解析与注入。话句话说,由DI容器提供的这个服务对象是一个” 开箱即用”的对象,这个对象自身直接或者间接依赖的对象已经在初始化的工程中被自动注入其中了。

举个简单的例子,我们创建一个名为Cat的DI容器类,那么我们可以通过调用具有如下定义的扩展方法GetService<T>从某个Cat对象获取指定类型的服务对象。我之所以将其命名为Cat,源于我们大家都非常熟悉的一个卡通形象“机器猫(哆啦A梦)”。它的那个四次元口袋就是一个理想的DI容器,大熊只需要告诉哆啦A梦相应的需求,它就能从这个口袋中得到相应的法宝。DI容器亦是如此,服务消费者只需要告诉容器所需服务的类型(一般是一个服务接口或者抽象服务类),就能得到与之匹配的服务对象。

   1: public static class CatExtensions
   2: {  
   3:     public static T GetService<T>(this Cat cat);
   4: }

对于我们在上一篇演示的MVC框架,我们在前面分别采用不同的设计模式对框架的核心类型MvcEngine进行了改造,现在我们采用DI的方式并利用上述的这个Cat容器按照如下的方式对其进行重新实现,我们会发现MvcEngine变得异常简洁而清晰。

   1: public class MvcEngine
   2: {
   3:     public Cat Cat { get; private set; }
   4:  
   5:     public MvcEngine(Cat cat)
   6:     {
   7:         this.Cat = cat;
   8:     }
   9:  
  10:     public void Start(Uri address)
  11:     {
  12:         while (true)
  13:         {
  14:             Request request = this.Cat.GetService<Listener>().Listen(address);
  15:             Task.Run(() =>
  16:             {
  17:                 Controller controller = this.Cat.GetService<ControllerActivator>().ActivateController(request);
  18:                 View view = this.Cat.GetService<ControllerExecutor>().ExecuteController(controller);
  19:                 this.Cat.GetService<ViewRenderer>().RenderView(view);
  20:             });
  21:         }
  22:     }
  23: } 

DI体现了一种最为直接的服务消费方式,消费者只需要告诉生产者(DI容器)关于所需服务的抽象描述,后者根据预先注册的规则提供一个匹配的服务对象。这里所谓的服务描述主要体现为服务接口或者抽象服务类的类型,当然也可以是包含实现代码的具体类型。至于应用程序对由框架控制的流程的定制,则可以通过对DI容器的定制来完成。如果具体的应用程序需要采用上面定义的SingletonControllerActivator以单例的模式来激活目标Controller,那么它可以在启动MvcEngine之前按照如下的形式将SingletonControllerActivator注册到后者使用的DI容器上。

   1: public class App
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Cat cat = new Cat().Register<ControllerActivator, SingletonControllerActivator>();
   6:         MvcEngine engine     = new MvcEngine(cat);
   7:         Uri address          = new Uri("http://localhost/mvcapp");
   8:         Engine.Start(address);
   9:     }
  10: }

二、三种依赖注入方式

一项确定的任务往往需要多个对象相互协作共同完成,或者某个对象在完成某项任务的时候需要直接或者间接地依赖其他的对象来完成某些必要的步骤,所以运行时对象之间的依赖关系是由目标任务来决定的,是“恒定不变的”,自然也无所谓“解耦”的说法。但是运行时的对象通过设计时的类来定义,类与类之间耦合则可以通过依赖进行抽象的方式来解除。

从服务使用的角度来讲,我们借助于一个服务接口对消费的服务进行抽象,那么服务消费程序针对具体服务类型的依赖可以转移到对服务接口的依赖上。但是在运行时提供给消费者总是一个针对某个具体服务类型的对象。不仅如此,要完成定义在服务接口的操作,这个对象可能需要其他相关对象的参与,换句话说提供的这个服务对象可能具有针对其他对象的依赖。作为服务对象提供者的DI容器,在它向消费者提供服务对象之前会自动将这些依赖的对象注入到该对象之中,这就是DI命名的由来。

如右图所示,服务消费程序调用GetService<IFoo>()方法向DI容器索取一个实现了IFoo接口的某个类型的对象,DI容器会根据预先注册的类型匹配关系创建一个类型为Foo的对象。此外,Foo对象依赖Bar和Baz对象的参与才能实现定义在服务接口IFoo之中的操作,所以Foo具有了针对Bar和Baz的直接依赖。至于Baz,它又依赖Qux,那么后者成为了Foo的间接依赖。对于DI容器最终提供的Foo对象,它所直接或者间接依赖的对象Bar、Baz和Qux都会预先被初始化并自动注入到该对象之中。

从编程的角度来讲,类型中的字段或者属性是依赖的一种主要体现形式,如果类型A中具有一个B类型的字段或者属性,那么A就对B产生了依赖。所谓依赖注入,我们可以简单地理解为一种针对依赖字段或者属性的自动化初始化方式。具体来说,我们可以通过三种主要的方式达到这个目的,这就是接下来着重介绍的三种依赖注入方式。

构造器注入

构造器注入就在在构造函数中借助参数将依赖的对象注入到创建的对象之中。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性Bar上,针对该属性的初始化实现在构造函数中,具体的属性值由构造函数的传入的参数提供。当DI容器通过调用构造函数创建一个Foo对象之前,需要根据当前注册的类型匹配关系以及其他相关的注入信息创建并初始化参数对象。

   1: public class Foo
   2: {
   3:     public IBar Bar{get; private set;}
   4:     public Foo(IBar bar)
   5:     {
   6:         this.Bar = bar;
   7:     }
   8: }

除此之外,构造器注入还体现在对构造函数的选择上面。如下面的代码片段所示,Foo类上面定义了两个构造函数,DI容器在创建Foo对象之前首选需要选择一个适合的构造函数。至于目标构造函数如何选择,不同的DI容器可能有不同的策略,比如可以选择参数做多或者最少的,或者可以按照如下所示的方式在目标构造函数上标注一个相关的特性(我们在第一个构造函数上标注了一个InjectionAttribute特性)。

   1: public class Foo
   2: {
   3:     public IBar Bar{get; private set;}
   4:     public IBaz Baz {get; private set;}
   5:  
   6:     [Injection]
   7:     public Foo(IBar bar)
   8:     {
   9:         this.Bar = bar;
  10:     }
  11:  
  12:     public Foo(IBar bar, IBaz):this(bar)
  13:     {
  14:         this.Baz = baz;
  15:     }
  16: }

属性注入

如果依赖直接体现为类的某个属性,并且该属性不是只读的,我们可以让DI容器在对象创建之后自动对其进行赋值进而达到依赖自动注入的目的。一般来说,我们在定义这种类型的时候,需要显式将这样的属性标识为需要自动注入的依赖属性,以区别于该类型的其他普通的属性。如下面的代码片段所示,Foo类中定义了两个可读写的公共属性Bar和Baz,我们通过标注InjectionAttribute特性的方式将属性Baz设置为自动注入的依赖属性。对于由DI容器提供的Foo对象,它的Baz属性将会自动被初始化。

   1: public class Foo
   2: {
   3:     public IBar Bar{get; set;}
   4:  
   5:     [Injection]
   6:     public IBaz Baz {get; set;}
   7: }

方法注入

体现依赖关系的字段或者属性可以通过方法的形式初始化。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性上,针对该属性的初始化实现在Initialize方法中,具体的属性值由构造函数的传入的参数提供。我们同样通过标注特性(InjectionAttribute)的方式将该方法标识为注入方法。DI容器在调用构造函数创建一个Foo对象之后,它会自动调用这个Initialize方法对只读属性Bar进行赋值。在调用该方法之前,DI容器会根据预先注册的类型映射和其他相关的注入信息初始化该方法的参数。

   1: public class Foo
   2: {
   3:     public IBar Bar{get; private set;}
   4:  
   5:     [Injection]
   6:     public Initialize(IBar bar)
   7:     {
   8:         this.Bar = bar;
   9:     }
  10: }

三、实例演示:创建一个简易版的DI容器

上面我们对DI容器以及三种典型的依赖注入方式进行了详细介绍,为了让读者朋友们对此具有更加深入的理解,介绍我们通过简短的代码创建一个迷你型的DI容器,即我们上面提到过的Cat。在正式对Cat的设计展开介绍之前,我们先来看看Cat在具体应用程序中的用法。

   1: public interface IFoo {}
   2: public interface IBar {}
   3: public interface IBaz {}
   4: public interface IQux {}
   5:  
   6: public class Foo : IFoo
   7: {
   8:     public IBar Bar { get; private set; }
   9:  
  10:     [Injection]
  11:     public IBaz Baz { get; set; }
  12:  
  13:     public Foo() {}
  14:  
  15:     [Injection]
  16:     public Foo(IBar bar)
  17:     {
  18:         this.Bar = bar;
  19:     }
  20: }
  21:  
  22: public class Bar : IBar {}
  23:  
  24: public class Baz : IBaz
  25: {
  26:     public IQux Qux { get; private set; }
  27:  
  28:     [Injection]
  29:     public void Initialize(IQux qux)
  30:     {
  31:         this.Qux = qux;
  32:     }
  33: }
  34:  
  35: public class Qux : IQux {}

我们在一个控制台应用中按照如上的形式定义了四个服务类型(Foo、Bar、Baz和Qux),它们分别实现了各自的服务接口(IFoo、IBar、IBaz和IQux)。定义在Foo中的属性Bar和Baz,以及定义在Baz中的属性Qux是三个需要自动注入的依赖属性,我们采用的注入方式分别是构造器注入、属性注入和方法注入。

我们在作为应用入口的Main方法中编写了如下一段程序。如下面的代码片段所示,在创建了作为DI容器的Cat对象之后,我们调用它的Register<TFrom, TTo>()方法注册了服务类型和对应接口之间的匹配关系。然后我们调用Cat对象的GetService<T>()方法通过指定的服务接口类型IFoo得到对应的服务对象,为了确保相应的依赖属性均按照我们希望的方式被成功注入,我们将它们显式在控制台上。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Cat cat = new Cat();
   6:         cat.Register<IFoo, Foo>();
   7:         cat.Register<IBar, Bar>();
   8:         cat.Register<IBaz, Baz>();
   9:         cat.Register<IQux, Qux>();
  10:  
  11:         IFoo service = cat.GetService<IFoo>();
  12:         Foo foo = (Foo)service;
  13:         Baz baz = (Baz)foo.Baz;
  14:  
  15:         Console.WriteLine("cat.GetService<IFoo>(): {0}", service);
  16:         Console.WriteLine("cat.GetService<IFoo>().Bar: {0}", foo.Bar);
  17:         Console.WriteLine("cat.GetService<IFoo>().Baz: {0}", foo.Baz);
  18:         Console.WriteLine("cat.GetService<IFoo>().Baz.Qux: {0}", baz.Qux);
  19:     }
  20: }

这段程序被成功执行之后会在控制台上产生如下所示的输出结果,这充分证明了作为DI容器的Cat对象不仅仅根据指定的服务接口IFoo创建了对应类型(Foo)的服务对象,而且直接依赖的两个属性(Bar和Baz)分别以构造器注入和属性注入的方式被成功初始化,间接依赖的属性(Baz的属性Qux)也以方法注入的形式被成功初始化。

   1: cat.GetService<IFoo>(): Foo
   2: cat.GetService<IFoo>().Bar: Bar
   3: cat.GetService<IFoo>().Baz: Baz
   4: cat.GetService<IFoo>().Baz.Qux: Qux

在对Cat容器的用法有了基本了解之后,我们来正式讨论它的总体设计和具体实现。我们首先来看看用来标识注入构造函数、注入属性和注入方法的InjectionAttribute特性的定义,如下面的代码片段所示,InjectionAttribute仅仅是一个单纯的标识特性,它的用途决定了应用该特性的目标元素的类型(构造函数、属性和方法)。

   1: [AttributeUsage( AttributeTargets.Constructor| 
   2:                  AttributeTargets.Property| 
   3:                  AttributeTargets.Method, 
   4:                  AllowMultiple = = false)]
   5: public class InjectionAttribute: Attribute {}

如下所示的是Cat类的完整定义。我们采用一个ConcurrentDictionary<Type, Type>类型的字段来存放服务接口和具体服务类型之间的映射关系,这样的映射关系通过调用Register方法实现。针对服务类型(服务接口类型或者具体服务类型均可)的服务对象提供机制实现在GetService方法中。

   1: public class Cat
   2: {
   3:     private ConcurrentDictionary<Type, Type> typeMapping = new ConcurrentDictionary<Type, Type>();
   4:  
   5:     public void Register(Type from, Type to)
   6:     {
   7:         typeMapping[from] = to;
   8:     }
   9:  
  10:     public object GetService(Type serviceType)
  11:     {
  12:         Type type;
  13:         if (!typeMapping.TryGetValue(serviceType, out type))
  14:         {
  15:             type = serviceType;
  16:         }
  17:         if (type.IsInterface || type.IsAbstract)
  18:         {
  19:             return null;
  20:         }
  21:  
  22:         ConstructorInfo constructor = this.GetConstructor(type);
  23:         if (null == constructor)
  24:         {
  25:             return null;
  26:         }
  27:  
  28:         object[] arguments = constructor.GetParameters().Select(p => this.GetService(p.ParameterType)).ToArray();
  29:         object service = constructor.Invoke(arguments);
  30:         this.InitializeInjectedProperties(service);
  31:         this.InvokeInjectedMethods(service);
  32:         return service;
  33:     }
  34:  
  35:     protected virtual ConstructorInfo GetConstructor(Type type)
  36:     {
  37:         ConstructorInfo[] constructors = type.GetConstructors();
  38:         return constructors.FirstOrDefault(c => c.GetCustomAttribute<InjectionAttribute>() != null)
  39:             ?? constructors.FirstOrDefault();
  40:     }
  41:  
  42:     protected virtual void InitializeInjectedProperties(object service)
  43:     {
  44:         PropertyInfo[] properties = service.GetType().GetProperties()
  45:             .Where(p => p.CanWrite && p.GetCustomAttribute<InjectionAttribute>() != null)
  46:             .ToArray();
  47:         Array.ForEach(properties, p =>p.SetValue(service, this.GetService(p.PropertyType)));
  48:     }
  49:  
  50:     protected virtual void InvokeInjectedMethods(object service)
  51:     {
  52:         MethodInfo[] methods = service.GetType().GetMethods()
  53:             .Where(m => m.GetCustomAttribute<InjectionAttribute>() != null)
  54:             .ToArray();
  55:         Array.ForEach(methods, m=> 
  56:         {
  57:             object[] arguments = m.GetParameters().Select(p => this.GetService(p.ParameterType)).ToArray();
  58:             m.Invoke(service, arguments);
  59:         });
  60:     }        
  61: }

如上面的代码片段所示,GetService方法利用GetConstructor方法返回的构造函数创建服务对象。GetConstructor方法体现了我们采用的注入构造函数的选择策略:优先选择标注有InjectionAttribute特性的构造函数,如果不存在则选择第一个公有的构造函数。执行构造函数传入的参数是递归地调用GetService方法根据参数类型获得的。

服务对象被成功创建之后,我们分别调用InitializeInjectedProperties和InvokeInjectedMethods方法针对服务对象实施属性注入和方法注入。对于前者(属性注入),我们在以反射的方式得到所有标注了InjectionAttribute特性的依赖属性并对它们进行赋值,具体的属性值同样是以递归的形式调用GetService方法针对属性类型获得。至于后者(方法注入),我们同样以反射的方式得到所有标注有InjectionAttribute特性的注入方法后自动调用它们,传入的参数值依然是递归地调用GetService方法针对参数类型的返回值。

ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core中的依赖注入(2):依赖注入(DI)
ASP.NET Core中的依赖注入(3):服务注册与提取
ASP.NET Core中的依赖注入(4):构造函数的选择与生命周期管理
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【总体设计】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【解读ServiceCallSite】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

时间: 2024-08-06 20:06:59

依赖注入(DI)的相关文章

iOS控制反转(IoC)与依赖注入(DI)的实现

背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大的方便,要在OC上较好的实现这两个功能,需要一些小小的技巧. 控制反转和依赖注入 控制反转 简单来说,将一个类对象的创建由手动new方式改为从IOC容器内获取,就是一种控制反转,例如我们现在要创建一个ClassA类,则常规方法为 ClassA *a = [ClassA new]; 如果使用控制反转,

控制反转IOC与依赖注入DI

1. IoC理论的背景我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦合的对象 如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针.分针和秒针顺时针旋转,从而在表盘上产生正确的时间.图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务.我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组

控制反转(Ioc)和依赖注入(DI)

控制反转IOC, 全称 “Inversion of Control”.依赖注入DI, 全称 “Dependency Injection”. 面向的问题:软件开发中,为了降低模块间.类间的耦合度,提倡基于接口的开发,那么在实现中必须面临最终是有“谁”提供实体类的问题.(将各层的对象以松耦合的方式组织起来,各层对象的调用面向接口.) 当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常有调用者来创建被调用者的实例. 然后,采用依赖注入原则,创建被调用者的实例的工作不再由调用者完成,而

spring(3)------控制反转(IOC)/依赖注入(DI)

一,spring核心概念理解 控制反转: 控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理. 所谓的"控制反转"概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器. 没有控制反转这种模式前,你创建一个对象,在什么地方用,你得单独通过关键字new出来用, 但现在可以不用这样,你把new对象的权利交给spring配置文件,通过配置文件来'new', 就是权利的反转,你以前干的事

话说 依赖注入(DI) or 控制反转(IoC)

首页 下载 扩展 应用 教程 代码 案例 资讯 讨论 全部 搜索 话说 依赖注入(DI) or 控制反转(IoC) 浏览:3641 发布日期:2014/03/20 分类:技术分享 科普:首先依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,鄙人学习了一下,看TP官网还没有相关的文章,就写下这篇拙作介绍一下这种设计模式,希望能为TP社区贡献一些力量. 首先先别追究这个设计模式的定义,否则你一定会被说的云里雾里,笔者就是深受其害,百度了N多文章,都是从理论角度来描

依赖注入(DI)和Ninject,Ninject

我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,Inversion of Control )是一个意思. DI是一种通过接口实现松耦合的设计模式. 依赖注入(DI)和Ninject Ninject简介

MVC进阶之路:依赖注入(Di)和Ninject

MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点.在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行. 图1 如图1所示,数据库操作类DataManager中依赖的IDataBase的接口,而不是以来IDataBase的具

AngularJS学习--- AngularJS中XHR(AJAX)和依赖注入(DI) step5

前言:本文接前一篇文章,主要介绍什么是XHR,AJAX,DI,angularjs中如何使用XHR和DI. 1.切换工具目录 git checkout -f step-5 #切换分支 npm start #启动项目 2.什么是XHR和依赖注入(Dependency Injection)? 1)什么是XHR? XHR是XMLHttpRequest的简称,XMLHttpRequest 用于在后台与服务器交换数据,主要是为了实现在不重新加载整个网页的情况下,对网页的某部分进行更新.简单说,浏览器中URL

控制反转IOC与依赖注入DI - 理论篇

学无止境,精益求精 十年河东十年河西,莫欺少年穷 昨天是五一小长假归来上班的第一天,身体疲劳,毫无工作热情.于是就看看新闻,喝喝茶,荒废了一天 也就在昨天,康美同事张晶童鞋让我学习下IOC的理论及实现,毕竟是之前的好同事,好朋友,我也就抽时间百度了很多资料 在查阅网上资料的过程中,我发现大多技术篇幅都是IOC的代码实现,并没有一篇介绍IOC理论的篇幅!这显然不是我想要的. 我知道要想搞明白IOC,就必须要弄明白什么是IOC(控制反转)?为什么叫IOC(控制反转)?为什么之后又可以称为DI(依赖注

ASP.NET Core 依赖注入(DI)

原文:ASP.NET Core 依赖注入(DI) ASP.NET Core的底层设计支持和使用依赖注入.ASP.NET Core 应用程序可以利用内置的框架服务将服务注入到启动类的方法中,并且应用程序服务也可以配置注入.由ASP.NET Core 提供的默认服务容器提供了最小功能集,并不是取代其他容器. 1.浅谈依赖注入 依赖注入(Dependency injection,DI)是一种实现对象和依赖者之间松耦合的技术,将类用来执行其操作的这些对象以注入的方式提供给该类,而不是直接实例化依赖项或者