文档目录html
本节内容:git
若是你已经知道依赖注入概念、构造器和属性注入,你能够跳到下一主题。数据库
维基百科:“依赖注入是软件设计模式,一个依赖对象(或客户端)须要一个或多个依赖(或服务)的注入,或传入引用做为它状态的一部分。该模式把客户端的依赖的建立和客户端行为分离开来,这样使得程序设计更加地松藕合,更符合依赖倒置及单一职责原则。它与客户端了解依赖关系的服务定位器模式造成对比。”设计模式
若是不使用依赖注入技术,很难管理依赖并开发出一个模块和结构良好的应用。框架
在一个应用里,类之间相互依赖。假设咱们有一个应用服务,它使用仓储插入实体到数据库,在这种状况下,应用服务类依赖于仓储类,以下:测试
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使用PersonReopsitory插入一个Person到数据库。这段代码的问题:spa
为克服这些问题,可使用工厂模式。把仓储的建立抽象出来。代码以下:.net
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这个实现。但仍有些问题:
在依赖注入上有几种最佳实践(模式)。
上面的示例代码能够改写成下面这样:
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);
构造器注入是一个完美的方式,建立一个类独立于依赖对象的建立。可是,上述代码也有些问题:
幸运地是:有依赖注入框架能自动管理依赖。
构造器注入为一个类的依赖的注入提供了一个完美的方式,这种方式你不能建立一个不提供依赖的类的实例,一样它是必须显式声明自身全部须要才能正确工做的强方式。
可是,在某些状况下,有些类依赖其它类,但也能够在不提供依赖的状况下工做,这在横切关注点(如日志)里常常遇到,一个类能够在没有日志的状况下工做,但当你提供一个日志记录器给它时,它也能写日志。在这种状况下,你能够定义一个公开的依赖属性,而不是构造器。考虑一下,咱们想在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对象后,给它设置一个日志记录器,PersonAppService就能够写日志,以下所示:
var personService = new PersonAppService(new PersonRepository()); personService.Logger = new Log4NetLogger(); personService.CreatePerson("Yunus Emre", 19);
假设Log4NetLogger实现了ILogger,并用它Log4Net库写日志,所以PersonAppService就能写日志了。若是咱们不设置日志记录器,它就不写日志。因此咱们就能够说PersonAppService的ILogger是一个可选的依赖。
几乎全部的依赖注入框架都支持属性注入。
有不少的能自动解析依赖的依赖注入框架,它们能够建立全部依赖的对象(递归的依赖),因此你只须要写好构造器或属性注入模式,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", 19);
咱们首先建立WindsorContainer容器,接着用它们的接口注册PersonRepository和PersonAppService,而后咱们要求容器建立一个IPersonAppService,它就会用依赖建立PersonAppService并返回。在这个简单的示例里使用DI框架,可能不能明显得看出好处来,可是考虑一下,若是你在一个真实的企业应用遇到不少类和依赖,此时状况就不一样了。固然,能够在使用前的其它地方注册依赖,也能够在一个应用启动时只注册一次。
注册咱们同时把对象生命周期(life cycle)声明为短暂的(transient),这就意味着,当咱们解析这个类型的对象时,就会建立一个新的实例。还有一些其它不一样的生命周期(如单例)。
当你按照最佳实践和一些约定写你的应用时,ABP已经几乎无形的使用了依赖注入框架。
在ABP里有多种不一样的方法,把类注册到依赖注入系统里。大部分状况,约定注册就已足够。
ABP会按照约定自动注册全部仓储、领域服务、应用服务、Mvc控制器和Web Api控制器。例如,你有一个IPersonAppService接口和一个实现了该接口的PersonAppService类:
public interface IPersonAppService : IApplicationService { //... } public class PersonAppService : IPersonAppService { //... }
ABP会自动注册它,由于它实现了IApplicationService接口(空的接口)。注册成暂时的(每处使用建立一个实例)。当你把IPersonAppService接口注入(用构造器注入)到一个类时,将会建立一个PersonAppService对象并自动传入构造器。
命名约定:很重要,例如你能够把PersonAppService改为MyPersonAppService或其它以“PersonAppService”为后缀的名称,因为IPersonAppService也是这个后缀,因此没有问题。可是你不能把它命名为”service“,若是你这么作了,IPersonAppService就不能自动注册了(自注册到ID框架,而不是用接口),因此,你只能手动注册。
ABP能够按照约定注册程序集,你能够告诉ABP按照约定注册你的程序集,它至关容易:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Aseembly.GetExecutingAssembly()获取包含此代码的程序集的一个引用。你也能够传递一个其它程序集给RegisterAssemblyByConvention方法,一般状况下,这些工做在一个模块开始初始化时就都已完成。更多信息请查看模块系统。
你能够经过实现IConventionalRegisterer接口,写你本身的约定注册类,而后在你的模块的预初始化里,调用IocManager.AddConventionalRegisterer方法,添加你的类。
你可能想注册一个特定的但不符合约定注册规则的类,ABP提供了捷径:ITransientDependency和ISingletonDependency接口。例如:
public interface IPersonManager { //... } public class MyPersonManager : IPersonManager, ISingletonDependency { //... }
用这种方式,你能够很容易地注册MyPersonManager。当须要注入一个IPersonManager时,就使用到MyPersonManager。注意,依赖被声明为单例。所以,只建立MyPersonManager的一个实例,并把这同一个实例传递给全部须要它的类。在首次使用时建立,并在应用的整个生命周期中使用。
若是约定注册没法彻底符合你的状况,你可使用IocManager或Castle Windsor,注册你的类和依赖。
你能够用IocManager注册依赖(通常在你的模块定义类的预初始化里):
IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);
你可使用IIocManger.IocContainer属性访问Castle Windsor容器并注册依赖。例如:
IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
更多信息请参考Windsor文档。
在你的应用某个须要使用IOC(控制反转)容器(又名为:DI框架)建立对象的地方,注册把你的类、依赖关系和生命期,告诉IOC容器。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系统最适当的方式。
你可能须要用直接解析你的依赖来代替构造器和属性注入。这应该尽可能避免,但有时却又无可避免。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例子,它用构造器注入IIocResolver并用它解析和释放对象。Resolve方法有几个重载能够用来解析,Release用来释放组件(对象)。若是你手动解析一个对象,记得调用Release,不然你的应用可能存在内存泄露的问题。为确保释放对象,尽量使用ResolveAsDisposable(如上面例子所示),它在using块的最后自动调用Release。
若是你想直接使用IOC容器(Castle Windsor)来解析依赖,你能够构造器注入IIocManager并使用IIocManager.IocContainer属性。若是你在一个静态的上下文里,或不可能注入IIocManager,最后的选择是:你能够在任何地方使用单例对象IocManager.Instance,但这种方式使用你的代码不易于测试。
另外
有些类须要在第一次使用前初始化,IShouldInitialize有一个Initialize方法,若是你实现了它,那么在建立你的对象以后(使用以前)就会自动调用你的Initialize方法。固然,你应该注入/解析这个对象,以便这一特性起做用。
Asp.net Mvc 和 Asp.net Web Api 集成
咱们必须调用依赖注入系统来解析依赖图上的根对象。在一个Asp.net Mvc应用里,它一般是一个Controller(控制器)类。咱们一样也能够在控制器里用构造器注入和属性注入模式。当一个请求到达咱们的应用,用IOC容器建立控制器和全部依赖递归解析。因此由谁来作这件事?由ABP经过扩展Mvc的默认控制器工厂自动完成。相似地,Asp.net Web Api也同样。并且你也没必要关系建立和销毁对象。
暂略
只要你依照上面的规则和结构,ABP简化和自动使用依赖注入。大部分状况你不用作更多的事,可是只要你须要,你能够直接使用Castle Windsor的全部能力来执行任何任务(像自定义注册,注入钩子,拦截器等等)。
英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Dependency-Injection