在充分理解整个ABP系统架构之前首先必须充分了解ABP中最重要的依赖注入思想,在后面会具体举出一些实例来帮助你充分了解ABP中的依赖注入思想,在了解这个之前我们首先来看看什么是依赖注入?来看看维基百科是怎样来介绍这个概念的。维基百科说:“依赖注入是一种软件设计模式,指一个或多个依赖(或服务)被注入,或通过引用传递,传入一个依赖对象(或客户端)并成为客户状态的一部分。模式通过自身的行为分离了客户依赖的创建,这允许程序设计是松耦合的,同时遵循依赖倒置和单一职责原则。与服务定位器模式直接进行对比,它允许客户了解他们用来查找依赖的机制。”

  如果你对容器的概念十分了解的话,那么你一定推崇使用依赖注入容器来管理依赖注入对象,如果不使用依赖注入技术,很难进行依赖管理、模块化开发和应用程序模块化。说了这么多使用依赖注入容器的好处,那么我们先来说一说传统开发方式存在哪些问题?

一  传统开发方式

  在一个应用程序中,类之间相互依赖。假设在我们定义的应用层,我们有一个应用程序服务,使用仓储(repository)类插入实体到数据库。在这种情况下,应用程序服务类依赖于仓储(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到数据库在,分析上面的代码的时候我们发现存在诸多的问题:

  A  PersonAppService通过IPersonRepository调用CreatePerson方法,所以这方法依赖于IPersonRepository接口,代替了PersonRepository具体类。但在PersonAppService的构造函数仍然依赖于PersonRepository。组件应该依赖于接口而不是实现。这就是所谓的依赖性倒置原则。

  B  如果PersonAppService亲自创建PersonPeository,这变得依赖到了一个特定的IPersonRepository接口实现,且不能使用另一个实现进行工作。因此,从实现分离接口变得无意义,硬依赖使得代码基于紧耦合和低重用。硬依赖(hard-dependency)使得代码紧密耦合和较低的可重用。

  C 我们可能需要在未来改变创建PersonRepository的方式。即,我们可能想让它创建为单例(单一共享实例而不是为每个使用创建一个对象)。或者我们可能想要创建多个类实现IPersonRepository并根据条件创建对象。在这种情况下,我们需要修改所有依赖于IPersonRepository的类。

  D  有了这样的依赖,很难(或不可能)对PersonAppService进行单元测试。  

为了克服这些问题,可以使用工厂模式。因此创建的仓储类是抽象的。看下面重构后的代码:

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的实现的对象,这个对象取决于PersonRepositoryFactory的Create方法。但是,仍然存在一些问题:

  A PersonAppService取决于PersonRepositoryFactory,这是更容易接受,但仍有一个硬依赖(hard-dependency)。

  B  为每个库或每个依赖项乏味的写一个工厂类/方法。

  C 测试性依然不好,由于很难使得PersonAppService使用mock实现IPersonRepository。

    二 ABP中的实现

既然有这些问题,那么我们来看看ABP中是怎么来解决这些问题的,这里主要有通过构造函数进行依赖注入和通过属性进行依赖注入的方式,还是通过上面的例子我们来分别说明。

  2.1  构造函数注入(Constructor injection)

 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", 19);  

  构造函数注入是一个完美的方法,使一个类独立创建依赖对象。但是,上面的代码有一些问题:

  A  创建一个PersonAppService变得困难。想想如果它有4个依赖,我们必须创建这四个依赖对象,并将它们传递到构造函数PersonAppService。

  B  从属类可能有其他依赖项(PersonRepository可能有依赖关系)。所以我们必须创建PersonAppService的所有依赖项,所有依赖项的依赖关系等等. .如此依赖关系使得我们创建一个对象变得过于复杂了。幸运的是依赖注入框架自动化管理依赖关系。  

  有许多依赖注入框架,都可以自动解决依赖关系。他们可以创建所有依赖项(递归地依赖和依赖关系)。所以你只需要依赖注入模式写类和类构造函数&属性,其他的交给DI框架处理。在良好的应用程序中,类甚至独立于DI框架。整个应用程序只会有几行代码或类,显示的与DI框架交互。

那么这里我们便来看看ABP框架中使用了什么依赖注入框架来管理这些彼此有依赖关系的依赖项。ABP的依赖注入基于 Castle Windsor框架。Castle Windsor最成熟的DI框架之一。还有很多这样的框架,如Unity,Ninject,StructureMap,Autofac等等。在使用一个依赖注入框架时,首先注册你的接口/类到依赖注入框架中,然后你就可以resolve一个对象。在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", 19);  

  我们首先创建了WindsorContainer,然后注册PersonRepository 和 PersonAppService及它们的接口,紧接着我们利用容器创建一个IPersonAppService实例,它创建PersonAppService对象及其依赖项并返回。在这个简单的示例中,使用DI框架也许不是那么简洁,但想象下,在实际的企业应用程序中你会有很多类和依赖关系。当然注册的依赖项只在程序启动的某个地方创建一次。请注意我们只是将对象声明为临时对象(transient)。这意味着每当我们创建这些类型的一个对象时,就会创建一个新的实例。有许多不同的生命周期(如Singletion单例模式,在整个生命周期内只存在一个唯一的实例,应用程序共享这个唯一的实例)。

  2.2 属性注入(Property Injection)

  采用构造函数的注入模式是一个完美的提供类的依赖关系的方式。通过这种方式,只有提供了依赖你才能创建类的实例。同时这也是一个强大的方式显式地声明,类需要什么样的依赖才能正确的工作。但是,在有些情况下,该类依赖于另一个类,但也可以没有它。这通常是适用于横切关注点(如日志记录),一个类可以没有工作日志但它可以写日志如果你提供一个日志对象。在这种情况下,你可以定义依赖为公共属性,而不是让他们放在构造函数。想想,如果我们想在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);
}
}   

  在这里NullLogger.Instance 是一个单例对象,实现了ILogger接口,但实际上什么都没做(不写日志。它实现了ILogger实例,且方法体为空)。现在PersonAppService可以写日志了,如果你为PersonAppService实例设置了Logger,可以通过下面的这种方式。

    var personService = new PersonAppService(new PersonRepository());
personService.Logger = new Log4NetLogger();
personService.CreatePerson("Yunus Emre", 19);  

  假设Log4NetLogger实现ILogger实例,使得我们可以使用Log4Net库写日志。因此PersonAppService可以写日志。如果我们不设置Logger,PersonAppService就不写日志。因此,我们可以说PersonAppService ILogger实例是一个可选的依赖。几乎所有的依赖注入框架都支持属性注入模式。

  2.3  ABP中的依赖注入

  在编写应用程序时遵循最佳实践和一些约定,ABP几乎让依赖注入框架使用变得无形。

  A 注册(Registering)

在ABP中,有很多种不同的方法来注册你的类到依赖注入系统。大部分时间,常规方法就足够了。

  B 常规注册(Conventional registrations)

  按照约定ABP自动注册所有 Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。例如,你可能有一个IPersonAppService 接口和实现类PersonAppService:

public interface IPersonAppService : IApplicationService
{
//...
} public class PersonAppService : IPersonAppService
{
//...
}

  在这个实例中,ABP会自动注册它,因为它实现IApplicationService接口(它只是一个空的接口)。它会被注册为transient (每次使用都创建实例)。当你注入(使用构造函数注入)IPersonAppService接口成一个类,PersonAppService对象会被自动创建并传递给构造函数。这里在定义实现类的时候需要注意:命名约定在这里非常重要。例如你可以将名字PersonAppService改为 MyPersonAppService或另一个包含“PersonAppService”后缀的名称,由于IPersonAppService包含这个后缀。但是你可以不遵循PeopleService命名你的服务类。如果你这样做,比如你将实现类定义为MyPersonAppServiceImpl它将不会为IPersonAppService自动注册(它需要自注册self-registration到DI框架),所以如果你想要你应该手动注册它。至于更深层次的原因你可以参考ABP源码中的实现来进行分析。

具体的解释我看了一下ABP的源码:
在ABP默认注册的类型中使用下面的代码
//Transient
context.IocManager.IocContainer.Register(
Classes.FromAssembly(context.Assembly)
.IncludeNonPublicTypes()
.BasedOn<ITransientDependency>()
.If(type => !type.GetTypeInfo().IsGenericTypeDefinition)
.WithService.Self()
.WithService.DefaultInterfaces()
.LifestyleTransient()
);
其中WithService.DefaultInterfaces()这个方法是使用Castle Winsor中的方法
里面有下面的注释:
// 摘要:
// Uses all interfaces that have names matched by implementation type name. Matches
// Foo to IFoo, SuperFooExtended to IFoo and IFooExtended etc
public BasedOnDescriptor DefaultInterfaces();  

ABP按照约定注册程序集。所以你应该告诉ABP按照约定注册你的程序集。IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());这个Assembly.GetExecutingAssembly()得到一个对包括此代码的程序集的引用。你可以通过RegisterAssemblyByConvention方法注册其他程序集。在你实际的代码中你可以在继承自AbpModule的类下面通过的重写基类的Initialize方法来完成程序集中依赖接口的注入问题,例如:

 public class DcsDomainModule : AbpModule {

        public override void PreInitialize() {
Configuration.Auditing.IsEnabledForAnonymousUsers = true; DcsLocalizationConfigurer.Configure(Configuration.Localization);
} public override void Initialize() {
IocManager.RegisterAssemblyByConvention(typeof(DcsDomainModule).GetAssembly());
}
}  

  你可以通过实现IConventionalRegisterer接口和调用IocManager。AddConventionalRegisterer方法编写自己的约定注册类。你应该将它添加到模块的pre-initialize方法中。

  C 帮助接口(Helper Interfaces)  

  在ABP中你可以注册一个特定的类,不遵循传统的约定制度规则。ABP提供了ITransientDependency和ISingletonDependency接口的快捷方法。例如:

 public interface IPersonManager
{
//...
} public class MyPersonManager : IPersonManager, ISingletonDependency
{
//...
}  

  以这种方式你可以很容易地注册MyPersonManager为Singleton。当需要注入IPersonManager时,MyPersonManager会被使用。注意依赖被声明为单例。因此创建的MyPersonManager同一个对象被传递给所有需要的类。只是在第一次使用时创建,那么应用程序的整生命周期使用的是同一实例。

  D 自定义/直接 注册(Custom/Direct registration)

  如果之前描述的方法还是不足以应对你的情况,你可以使用Castle Windsor注册类和及依赖项。因此你将拥有Castle Windsor注册的所有能力,可以实现IWindsorInstaller接口进行注册,你可以在应用程序中创建一个实现IWindsorInstaller接口的类:

public class MyInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
}
}

  在这里你将直接使用ABP中直接使用的Castle Windsor注册容器来注册任何你想直接进行注册的接口及相应实现。

  E 构造函数 & 属性注入(Constructor & Property Injection)

  在ABP中作为最佳实践,你应该使用构造函数和属性注入去获取你的类的依赖。任何可能的地方,你都应该这样做。例如:

 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系统最适当的方式。

  F IIocResolver 和 IIocManager接口

  有时你可能需要直接创建你的依赖项,而不是构造函数和属性注入。应该尽可能避免这种情况,但它可能无法避免。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通过构造函数注入,然后用它来创建和释放对象。有几个解决方法的重载可以根据需要使用。Release方法用于释放组件(对象)。如果你是手动创建一个对象,调用Release方法释放对象非常重要。否则,你的应用程序会有内存泄漏问题。为了保证对象被释放,尽可能使用ResolveAsDisposable(就像上面的例子所示)。它会在using代码块结束的时候自动调用Release方法。

  如果你想直接使用IOC容器(Castle Windsor)来处理依赖关系项,可以通过构造函数注入 IIocManager并使用它IIocManager.IocContainer 属性,如果你是在一个静态上下文或不能注入IIocManager,还有最后一个方法,你可以使用单例对象IocManager.Instance,你可以在任何地方获取到,它无处不在。但是,在这种情况下你的代码将变得不容易测试。

  上面介绍了整个ABP系统中的依赖注入的原理,后面所有的内容都将依赖于这部分内容,所以希望通过这些事例使得对整个依赖注入框架有一个更加清晰的认识。

  最后,点击这里返回整个ABP系列的主目录。

ABP中的依赖注入思想的更多相关文章

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

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

  2. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  3. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  4. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

    本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...

  5. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

  6. Spring学习(一)——Spring中的依赖注入简介【转】

      [前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring. ...

  7. NET Core 中的依赖注入

    NET Core 中的依赖注入 [共7篇] 一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制, ...

  8. 类比Spring框架来实现OC中的依赖注入

    如果你之前使用过JavaEE开发中的Spring框架的话,那么你一定对依赖注入并不陌生.依赖注入(DI: Dependency Injection)是控制反转(IoC: Inversion of Co ...

  9. 深入理解net core中的依赖注入、Singleton、Scoped、Transient(四)

    相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...

随机推荐

  1. 使用 ASP.NET Core MVC 创建 Web API(三)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 十 ...

  2. 2.4配置的热更新「深入浅出ASP.NET Core系列」

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. 大家知道通常我们修改网站的配置文件比如,webconfig的时候,网站需要重启才能读取到最新的修改,所谓热更新就是 ...

  3. .net 简单实用Log4net(多个日志配置文件)

    前言: 几乎所有的大型应用都会有自己的用于跟踪调试的API.因为一旦程序被部署以后,就不太可能再利用专门的调试工具了.然而一个管理员可能需要有一套强大的日志系统来诊断和修复配置上的问题.所以这个时候就 ...

  4. MySql给表添加列和注释

    1.给表添加列 ALTER TABLE supplier_seller ADD COLUMN company_id INT NULL COMMENT '供应主体id'; 默认情况下,添加的列会添加到最 ...

  5. Java 数组及数组常用算法

    1 数组也是一种类型 Java中要求所有的数组元素具有相同的数据类型.因此在一个数组中,数组元素的类型是唯一的,不能存储多种类型的数据. 一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此 ...

  6. JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!

    摘要: 理解JS执行原理. 原文:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 本文是旨在深入研究JavaScrip ...

  7. es6 字符串的扩展和数值的扩展

    es6字符串的扩展 1. es6新增的一些方法 1.1 includes 判断是否包括在内,返回一个 true or false 1.2 statsWith 判断是否以什么开头,返回一个 true o ...

  8. Servlet常用的接口和类

    使用接口和类的作用:Servlet也是依靠继承父类和实现接口来实现的.使用Servlet必须要引入两个包:javax.servlet和javax.servlet.http.所有的Servlet应用都是 ...

  9. MySQL-每日定点运行

    最近做项目的时候设计每天定点执行的脚本,所以在这 马克一下,方便查找 set time_zone = '+8:00';      set GLOBAL event_scheduler = 1;     ...

  10. HTML之body标签中的相关标签补充

    一 列表标签 列表标签分为三种. 1.无序列表<ul>,无序列表中的每一项是<li> 英文单词解释如下: a.ul:unordered list,“无序列表”的意思. b.li ...