原文地址:https://aspnetboilerplate.com/Pages/Documents/Dependency-Injection

  • 什么是依赖注入
    • 传统方式的问题
    • 解决方案
      • 构造函数注入
      • 属性注入
      • 依赖注入框架
  • ABP依赖注入基础设施
    • 注册依赖项
      • 常用注册
      • 帮助接口
      • 自定义/直接注册
        • 使用IocManager
    • 解析
    • 构造函数&属性注入
    • IIocResolver, IIocManager and IScopedIocResolver
    • 附加部分
      • IShouldInitialize 接口
    • ASP.NET MVC & ASP.NET Web API 集成
    • ASP.NET Core 集成
    • 最后备注

什么是依赖注入

如果你已经知道了依赖注入的概念、构造函数和属性注入模式,那你可以跳到下一节。
 
维基百科说:“依赖注入是一种软件设计模式,它注入一个或多个依赖项(或服务),或者通过引用传递到一个依赖对象(或客户端),并成为客户端状态的一部分。这种设计模式使得客户端的依赖项的创建与其行为本身分离开来,这让程序设计松耦合、遵循依赖反转和单一职责原则,这与服务定位器模式形成了一个鲜明的对比,服务定位器模式允许客户端清楚它用来寻找依赖的系统”。
 
如果不使用依赖注入技术,我们很难管理依赖关系及开发一个模块化和结构良好的应用程序。

传统方式的问题

在一个应用程序中,类彼此依赖,假设我们有一个使用 repository 插入entities到数据库的application service ,在这种情况下,这个application service是依赖于repository这个类的。如下面的例子:
 
public class PersonAppService
{
private IPersonRepository _personRepository; public PersonAppService()
{
_personRepository = new PersonRepository();
} public void CreatePerson(string name, int age)
{
var person = new Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
PersonAppService 使用PersonRepository 插入一个 Person 到数据库里,这代码的问题是:
  • PersonAppService在CreatePerson方法里使用了IPersonRepository的引用,所以这个方法依赖于IPersonRepository,而不是依赖于PersonRepository,但是,在PersonAppService的构造函数中依然依赖于PersonRepository。组件应该依赖于接口而不是实现类。这就是依赖反转原则。
  • 如果PersonAppService创建PersonRepository本身,这样就变成了依赖IPersonRepository接口具体的实现类了,同时使得它不能用IPersonRepository接口的其它实现类,这样,将接口与实现类分离就毫无意义了。强依赖关系使得代码基础紧密耦合,且可重用性较低。
  • 我们在以后可能需要改变PersonRepository的创建,比如说,我们可能想把它创建成一个单例(单一共享一个实例,而不是每次用时都创建一个对象),或者我们可能想创建多个IPersonRepository接口的实现类,而我们是根据条件来创建其中某个实现类的实例。在这种情况下,我们就得修改所有依赖于IPersonRepository接口的类。
  • 有了这样的依赖关系,我们是很难(或者不可能)对PersonAppService进行单元测试。
 
为了解决这样的问题,工厂模式可以起到作用,因此,创建repository类是抽象的。如下面代码:
 
public class PersonAppService
{
private IPersonRepository _personRepository; public PersonAppService()
{
_personRepository = PersonRepositoryFactory.Create();
} public void CreatePerson(string name, int age)
{
var person = new Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
PersonRepositoryFactory 是用来创建和返回一个IPersonRepository对象的静态类,这就是服务定位器模式。因为PersonAppService 不知道如何创建IPersonRepository实现类对象,而且它与PersonRepository 的实现无关,所以创建的问题被解决了,但是这样还是存在一些问题:
  • 这次,PersonAppService 依赖于PersonRepositoryFactory ,这个相当来说还可以接受,但是这样还是一种强依赖关系。
  • 为每一个repository 或它的依赖类写一个工厂类或方法是很乏味的。
  • 这样可测试性还是不好,因为很难让PersonAppService 去使用一些模拟实现IPersonRepository的对象。

解决方案

对于依赖其它的类,还有一些最佳实践(模式)。

构造函数注入模式

上面的例子可以重写成下面这样:
 
public class PersonAppService
{
private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
} public void CreatePerson(string name, int age)
{
var person = new Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
这就是构造函数注入。现在,PersonAppService 不知道那个类实现了IPersonRepository,而且不知道如何去创建实例的。谁需要用到PersonAppService ,首先要创建一个IPersonRepository实现类对象,并把它传递给PersonAppService 的构造函数,如下所示:
 
var repository = new PersonRepository();
var personService = new PersonAppService(repository);
personService.CreatePerson("Yunus Emre", );
构造函数注入是使类独立于创建依赖对象的一种完美的解决方式,但是,上面的代码还是有一些问题:
  • 创建一个PersonAppService 对象变得更难了。试想一下,如果PersonAppService 依赖于4个类,那我们就必须创建这4个依赖类的对象,并把它们传递给PersonAppService 的构造函数。
  • 依赖类可能也有它们自己的依赖类(这里,PersonRepository可能还依赖其它的类),因此,我们必须创建所有PersonAppService 所依赖的对象,以及这些依赖对象所依赖类的对象,如此等等。这样,我们甚至连一个对象都创建不了了,因为这依赖关系它复杂了。
 
幸运的是,有依赖注入框架去自动管理这些依赖关系。

属性注入模式

构造函数注入模式是一种解决类依赖问题的完美方式,这样,在不能提供类的依赖项的情况下,你就不能创建这个类的实例,它也是明确声明这个类需求是如何正确地工作的一种强有力的方式。
 
但是,在有些情况下,这个类即使没有它所依赖的另一个类,它也要能正常运行, 对于诸如日志记录这样横切关注点的需求是很正常的。一个类可以在没有提供日志记录对象的情况下能正常运行,但是如果你提供了一个日志记录对象,它就能记录日志了。在这种情况下,你可以将依赖项定义为一个公共属性,而不是将它们放在构造函数中。试想一下,我们想要在PersonAppService里写日志,我们可以像下面所示的代码一样来重写这个类:
 
public class PersonAppService
{
public ILogger Logger { get; set; } private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
Logger = NullLogger.Instance;
} public void CreatePerson(string name, int age)
{
Logger.Debug("Inserting a new person to database with name = " + name);
var person = new Person { Name = name, Age = age };
_personRepository.Insert(person);
Logger.Debug("Successfully inserted!");
}
}
NullLogger.Instance是一个实现了ILogger接口的单例对象,但事实上它里面什么也没有(不记录日志,它实现ILogger接口的方法是空方法体),所以,现在,如果在创建PersonAppService 对象后,设置Logger,PersonAppService 就可以写日志了,如下所示:
 
var personService = new PersonAppService(new PersonRepository());
personService.Logger = new Log4NetLogger();
personService.CreatePerson("Yunus Emre", );
假设Log4NetLogger 实现ILogger,而且使用Log4Net类库来写日志,因此,PersonAppService 事实上是可以写日志的。如果我们不设置Logger,它就不能写日志,所以,我们可以说ILogger是PersonAppService的可选依赖项。
 
几乎所有依赖注入框架都支持属性注入模式。 

依赖注入框架

网上有很多可以自动解析依赖关系的依赖注入框架,它可以创建具有所有依赖项的对象(并递归依赖项的依赖项),所以,你可以用构造函数&属性注入模式来写你的类,DI框架为你处理剩下的事!在一个好的应用程序里,你的类是独立的,甚至独立于DI框架。在你整个应用程序里,你要写几行代码或类来声明与DI框架交互。
 
ABP使用了Castle Windsor 依赖注入框架,这是一个最成熟的依赖注入框架之一,网络上还有其它一些DI框架,如Unity, Ninject, StructureMap, Autofac等等。
 
在依赖注入框架里,你首先得注册你的接口或类至依赖注入框架里,然后你就可以解析(创建)对象。在Castle Windsor里,会有像下面这样的代码:
 
var container = new WindsorContainer();

container.Register(
Component.For<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(),
Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient()
); var personService = container.Resolve<IPersonAppService>();
personService.CreatePerson("Yunus Emre", );
我们先创建WindsorContainer对象,然后使用注册了PersonRepository 和PersonAppService的接口,再然后,我们让container去创建IPersonAppService的对象,container创建带有依赖项的PersonAppService 对象并返回。也许在这个简单的例子里,我们不能明显地看出使用DI框架的优势,但是想想,在一个真实的企业应用程序里,你往往是有许多的类及依赖关系。当然,注册依赖项是在创建和使用这些对象之外的地方,而且你在应用程序启动时只注册一次。
 
注意我们上面也声明对象的生命周期transient,这意味着无论我们什么时候解析这些类型的对象时,都会创建一个新的实例。生命周期有很多种(比如单例)。

ABP依赖注入基础设施层

当你按照最佳实践和一些惯例来写你的应用程序,ABP几乎没有显露出使用了依赖注入框架痕迹。 

注册依赖项

 在ABP中,有几种不同的方式注册你的类到依赖注入系统里,大多数时候,用常规注册方法就足够了。 

常规注册

 ABP自动注册所有RepositoriesDomain ServicesApplication Services,MVC Controllers and Web API Controllers。比如,你可能有一个IPersonAppService 接口和实现这个接口的PersonAppService类:
 
public interface IPersonAppService : IApplicationService
{
//...
} public class PersonAppService : IPersonAppService
{
//...
}
ABP自动注册它,因为它实现了IApplicationService 接口(它只是一个口接口)。它注册为transient (每次使用都创建一个实例)。当你注入(使用构造函数注入)IPersonAppService接口给一个类时,自动会创建一个PersonAppService对象,并把它传递到构造函数里。
 
在这里命名约定是很重要的。比如你可以把PersonAppService的名字改为MyPersonAppService或其他包含了“PersonAppService”为后缀的名字,因为IPersonAppService 有这个后缀,但是你不可以把你的service类命名为PeopleService,如果你这样做的话,它就不能自动注册为IPersonAppService (它注册到了DI框架里,但是以其本身注册的,而不是注册为接口),所以,如果你想这样做,你应该手动注册。
 
ABP可以通过常规方式来注册程序集,你可以告诉ABP用常规方式去注册你的程序集,这实现起来非常容易:
 
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Assembly.GetExecutingAssembly() 取得包含这代码的程序集引用,你可以传递其它程序集到RegisterAssemblyByConvention方法,当你的模块初始化完成时,这些基本就完成了。想了解更多可以去看ABP模块系统
 
通过实现IConventionalRegisterer接口,和在你的类里调用IocManager.AddConventionalRegisterer方法,你可以写你自己的常规注册类,你应该把它放在你的模块里的pre-initialize 方法里。

帮助接口

 你可能想注册一个不符合常规注册规则的特定的类,ABP提供了ITransientDependency ISingletonDependency接口作为一种快捷方式,例如:
 
public interface IPersonManager
{
//...
} public class MyPersonManager : IPersonManager, ISingletonDependency
{
//...
}
这样,你就可以轻易地注册MyPersonManager类。当你需要注册IPersonManager时,使用MyPersonManager。注意依赖被声明为单例,因此,创建MyPersonManager的单例,并且是把这同一个对象传递给所有需要它的类里,它只在第一次使用时创建,然后在应用程序的生命周期里都一直使用同一个实例。 

自定义/直接注册

 对于你的情况,如果常规注册不足以应付,你可以使用 IocManager 或Castle Windsor去注册你的类和依赖项。

使用IocManager

你可以使用IocManager 去注册依赖关系(通常是写在你的模块定义类的PreInitialize 方法里)
 
IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);

使用Castle Windsor API

你可以使用IIocManager.IocContainer 属性去访问Castle Windsor容器去注册依赖关系。比如:
 
IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());

更多信息,可以参考Windsor文档

解析

 注册通知IOC(控制反转)容器(有名DI框架)关于你的类、它们的依赖关系和生命周期,在你应用程序的其它地方需要使用IOC容器去创建对象时,ASP.NET提供了一些解析依赖关系的选项。 

构造函数&属性注入

 作为最佳实践,你可以使用构造函数和属性注入去取得依赖关系,你应该在任何地方都按这个方式来做。例如:
 
public class PersonAppService
{
public ILogger Logger { get; set; } private IPersonRepository _personRepository; public PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
Logger = NullLogger.Instance;
} public void CreatePerson(string name, int age)
{
Logger.Debug("Inserting a new person to database with name = " + name);
var person = new Person { Name = name, Age = age };
_personRepository.Insert(person);
Logger.Debug("Successfully inserted!");
}
}
IPersonRepository从构造函数注入,ILogger通过公用属性注入。这样,你的代码对依赖注入系统毫无所知了,这是使用DI系统最合适的方式。

IIocResolver, IIocManager and IScopedIocResolver

你可能需要直接创建你的依赖项,而不是通过构造函数&属性注入,如果可能的话,应该尽量避免这样的情况,但是有时是无法避免的。ABP提供一些服务使得注入和使用都变得容易。比如:
 
public class MySampleClass : ITransientDependency
{
private readonly IIocResolver _iocResolver; public MySampleClass(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
} public void DoIt()
{
//Resolving, using and releasing manually
var personService1 = _iocResolver.Resolve<PersonAppService>();
personService1.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
_iocResolver.Release(personService1); //Resolving and using in a safe way
using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>())
{
personService2.Object.CreatePerson(new CreatePersonInput { Name = "Yunus", Surname = "Emre" });
}
}
}
在应用程序里的示例类MySampleClass,通过构造函数注入IIcResolver ,并且使用它来解析和释放对象,如果需要,Resolve有一些重载方法可供使用,Release 方法被用来释放组件(对象),如果你手动创建了一个对象,调用Release方法是很关键的,否则,你的应用程序可能会有内存泄露的问题。为了确保释放了对象,无论什么地方尽可能使用ResolveAsDisposable (如上面例子中所示的),它在using块结束时自动调用Release方法。
 
IIocResolver (和IIocManager)也有一个CreateScope扩展方法(定义在Abp.Dependency命名空间下)来确保所有已创建的对象被安全地释放,如:
 
using (var scope = _iocResolver.CreateScope())
{
var simpleObj1 = scope.Resolve<SimpleService1>();
var simpleObj2 = scope.Resolve<SimpleService2>();
//...
}
在using块结束时,所有已创建的对象都自动被移除,使用IScopedIocResolver scope也是可注入的。你可以注入这个接口和解析依赖关系。当你的类释放后,所有已解析的依赖项都将被释放,但是要谨慎地使用它;比如,你的类的生命周期很长(如你的类是单例),而且你使用它创建太多的对象,然后这些对象将一直保留在内存里,直到你的类被释放。
 
如果你想直接用IOC容器(Castle Windsor)去解析依赖关系,你可以通过构造函数注入IIocManager,并使用IIocManager.IocContainer属性。如果在静态上下文里或不能注入IIocManager,在最后你还可以使用单例对象IocManager.Instance,但是,这样你的代码将变得不易测试。

附加部分

IShouldInitialize 接口

一些类在第一次使用前,需要先初始化,IShouldInitialize 有一个 Initialize()方法。如果你实现了它,然后在创建你的类的对象(使用前)后,会自动调用你的Initialize()方法。当然,你应该通过注入/解析对象来完成这个功能。

ASP.NET MVC & ASP.NET Web API 集成

我们必须调用依赖注入系统来解析依赖图中的根对象,在ASP.NET MVC 应用程序里,它通常是一个Controller类。我们也可以在控制器里使用构造函数注入和属性注入模式,当一个请求到达我们的程序里时,使用IOC容器来创建这个控制器,并递归解析所有依赖关系。所以,这个谁来做?这是ABP通过扩展ASP.NET MVC的默认控制器工厂来自动完成的。同此,对于ASP.NET Web API 也是一样的。你可以不用关心对象的创建和释放。

ASP.NET Core集成

ASP.NET Core使用Microsoft.Extensions.DependencyInjection 包内嵌了依赖注入系统,在ASP.NET Core里,ABP使用Castle.Windsor.MsDependencyInjection包集成它的依赖注入系统,所以,你可以不用考虑这些。

最后备注

只要你遵循ABP的规则和使用上面的结构,ABP简化和自动使用依赖注入,大部分情况下,这就足够使用了,但是如果你需要,你可以直接使用Castle Windsor的强大功能来执行任何任务(如自定义注册、注入钩子、拦截器等等)。

(译)ABP之依赖注入的更多相关文章

  1. ABP的依赖注入

    目录 说说ABP的依赖注入 代码追踪 说说ABP的依赖注入 上篇abp运行机制分析分析了ABP在启动时,都做了那些事:这篇我们来说说ABP的最核心的一部分:依赖注入(DependencyInjecti ...

  2. ABP框架 - 依赖注入

    文档目录 本节内容: 什么是依赖注入 传统方式的问题 解决方案 构造器注入模式 属性注入模式 依赖注入框架 ABP 依赖注入基础 注册依赖 约定注入 辅助接口 自定义/直接 注册 使用IocManag ...

  3. ABP之依赖注入

    写在开头 ABP开源项目最近有点小火,还开展了线下活动.本着学习DDD的心态与学习开源代码的的好奇,我也看了一遍ABP源码,在此将自己学习ABP的一些心得记录下来. 作为核心的IoC 作为一种解耦的方 ...

  4. [译] 关于 Angular 依赖注入你需要知道的

    如果你之前没有深入了解 Angular 依赖注入系统,那你现在可能认为 Angular 程序内的根注入器包含所有合并的服务提供商,每一个组件都有它自己的注入器,延迟加载模块有它自己的注入器. 但是,仅 ...

  5. Asp.Net Core 3.1 Api 集成Abp项目依赖注入

    Abp 框架 地址https://aspnetboilerplate.com/ 我们下面来看如何在自己的项目中集成abp的功能 我们新建core 3.1 API项目和一个core类库 然后 两个项目都 ...

  6. ABP(现代ASP.NET样板开发框架)系列之6、ABP依赖注入

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  7. ABP源码分析六:依赖注入的实现

    ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架.2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入 ...

  8. ABP理论学习之依赖注入

    返回总目录 本篇目录 什么是依赖注入 传统方式产生的问题 解决办法 依赖注入框架 ABP中的依赖注入基础设施 注册 解析 其他 ASP.NET MVC和ASP.NET Web API集成 最后提示 什 ...

  9. ABP依赖注入

    ABP依赖注入 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.N ...

随机推荐

  1. Java基础总结--面向对象2

    1.存在相关的多个方法就封装在一个类中,方法没调用到特有数据,需要静态化2.假如一个类所有方法都是静态方法,为了保证不被其他创建对象,可以将该类的构造方法私有化3.文档注释javadoc-按照规定注释 ...

  2. oracle实用基础

    Oracle修改字段名 alter table 表名 rename column 旧字段名 to 新字段名例子:alter table T_STUDENT rename column NAME to ...

  3. 数据分析与展示——NumPy库入门

    这是我学习北京理工大学嵩天老师的<Python数据分析与展示>课程的笔记.嵩老师的课程重点突出.层次分明,在这里特别感谢嵩老师的精彩讲解. NumPy库入门 数据的维度 维度是一组数据的组 ...

  4. x86-64栈帧中的“红色区域” red zone of stack frame on x86-64

    前几天看System V AMD64 ABI标准的时候发现栈帧的顶部后面有一块"red zone",在学cs:app3e/深入理解操作系统的时候并没有遇到这个,总结一下. 引用标准 ...

  5. Tinyhttpd阅读笔记

    1.简介 tinyhttpd是一个开源的超轻量型Http Server,阅读其源码,可以对http协议,微型服务器有进一步的了解. 源码链接: 参考博客:tinyhttpd源码分析 2.笔记 ---- ...

  6. [Bayesian] “我是bayesian我怕谁”系列 - Variational Autoencoders

    本是neural network的内容,但偏偏有个variational打头,那就聊聊.涉及的内容可能比较杂,但终归会 end with VAE. 各个概念的详细解释请点击推荐的链接,本文只是重在理清 ...

  7. 详细图解window环境mongodb下载、安装、配置与使用

    到官网下载最新版面mongodb安装包,(32位版本的已经取消了,只有64位的) 官网地址: https://www.mongodb.com/download-center#community 下载完 ...

  8. robotframework自动化系列:随机下拉框

    robotframework自动化系列:随机下拉框 随着项目自动化不断推进,在下拉框定位的时候出现些问题,每次下拉框选择都是相同的下拉选项,如果想每次选择的选项不一样,该如何实现呢,查找了很多资料,没 ...

  9. js数组元素的添加和删除

    简单测试例子: var arr = new Array(); arr[0] = "aaa"; arr[1] = "bbb"; arr[2] = "cc ...

  10. NoSQL:linux操作memcached

    缓存数据库 一 NoSQL简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",泛指非关系型的数据库,随着互联网web2.0网站的兴起,传统的关系 ...