依赖注入html
若是你已经知道依赖注入,构造函数和属性注入模式,能够直接跳到下一部分。github
维基百科说:“依赖注入是一种软件设计模式,一个或多个依赖项(或服务)被注入或经过引用传递到一个依赖对象,而且成为客户端状态的一部分。这种模式把客户端依赖项的建立从它本身的行为中分离出来,容许程序设计成松耦合的,遵循依赖倒置和单一职责的原则。和服务定位器模式相比,它容许客户端知道他们使用的系统查找依赖项。”web
不使用依赖注入技术,很难管理依赖项和发布一个结构良好的应用。数据库
在一个应用里,类之间相互依赖。假设咱们有一个使用仓储插入实体到数据的应用服务,在这种状况下,这个应用服务类依赖仓储类。以下示例:api
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实体。这段代码有如下问题:安全
可使用工厂模式克服这些问题中的一部分。所以,仓库类的建立应该是抽象的。请看以下代码:mvc
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对象后设置记录器,它就能够写日志了:
var personService = new PersonAppService(new PersonRepository()); personService.Logger = new Log4NetLogger(); personService.CreatePerson("Yunus Emre", 19);
假定Log4NetLogger实现了ILogger,且使用Log4Net类库写日志。从而,PersonAppService实际上能够写日志了。若是咱们没有设置记录器,它就不写日志。因此,咱们能够说ILogger是PersonAppService一个可选的依赖项。
几乎全部的依赖注入框架都支持属性注入模式。
有许多能够自动解析依赖项的依赖注入框架。他们能够建立对象以及其全部依赖项(能够递归依赖的依赖)。因此,只要使用构造函数、属性注入模式写类,DI框架会处理剩下的工做!在一个好的应用里,类甚至独立于DI框架。在整个应用里,只有几行或几个类显示的与DI框架交互。
ABP使用Castle Windsor做为依赖注入框架。它是最成熟的框架之一。还有许多其余的框架,如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框架的优点并非很明显,可是设想一下在一个真实的企业应用里将会有许多的类和依赖项。固然,注册依赖项只在应用启动的时候注册一次,建立和使用对象可能会在其余的地方。
注意,咱们把对象的生命周期生命为短暂的。意味着不管何时咱们须要类型的一个对象时,一个新的实例将会被建立。还有许多其余不一样的生命周期(如单例)。
当你遵循最佳实践和一些约定编写应用的时候,ABP几乎使依赖注入框架的使用无感知的。
在ABP中有不一样的方式注册类到依赖注入系统。大多数时候,常规的注册就足够了。
ABP默认会自动注册全部的仓储、领域服务、应用服务、MVC控制器和Web API控制器。例如,你有一个IPersonAppService接口和一个它的实现类PersonAppService:
public interface IPersonAppService : IApplicationService { //...
} public class PersonAppService : IPersonAppService { //...
}
由于它实现了IApplicationService(是一个空接口),因此ABP会自动注册它。它会被注册为暂时的(每次使用时建立实例)。当你注入(使用构造函数注入)IPersonAppService接口到一个类的时候,PersonAppService对象会被建立而且自动传递给构造函数。
命名约定在这里是很是重要的。例如,你能够把PersonAppService的名称改成MyPersonAppService或者其余包含'PersonAppService'后缀的名字,由于IPersonAppService有这个后缀。可是不能够把服务命名为PeopleService。若是这样作了,就不会自动注册IPersonAppService了(它使用自注册方式注册到DI框架,不使用接口),因此,若是想要注册的话能够手动注册。
ABP能够根据约定注册程序集。可让ABP根据约定注册程序集。这是至关简单的:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Assembly.GetExeutingAssembly()获取包含这些代码的程序集引用。你能够传递其余程序集到RegisterAssemblyByConvention方法。这些一般在模块初始化的时候执行。可参见ABP模块系统了解更多。
能够编写自定义的约定注册类,须要实现IConventionalRegisterer接口,而且在类里调用IocManager.AddConventionalRegiisterer方法。须要添加到模块的preinitialize方法中。
你可能想注册一个特定的类,但这个类不符合约定注册规则。ABP提供了ITransientDependency和ISingletonDependency接口做为捷径。例如:
public interface IPersonManager { //...
} public class MyPersonManager : IPersonManager, ISingletonDependency { //...
}
这样,就能够很容易注册MyPersonManager。当须要注入IPersonManager时,会使用MyPersonManager类。注意,依赖声明为单例的。所以,MyPersonManager建立为单例的,全部须要的类都会传入相同的对象。只有在首次使用的时候建立,在应用的整个生命周期都会使用相同的实例。
若是约定注册不能知足的话,一颗使用IocManager或Castle Windsor注册类和依赖项。
可使用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(Inversion of control)容器(a.k.a 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系统最合适的方式。
IIocResolver,IIocManager和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,做用范围也是能够注入的。能够注入这个接口用来解析全部依赖项。当类被释放的时候,全部解析的依赖项也会被释放。可是,须要当心使用;例如,若是有个类有很长的生命(比方是单例)而且解析了大量的对象,全部的这些类都会存在内存中,直到这个类被释放。
若是想直接使用IOC容器(Castle Windsor)解析依赖项,可使用构造函数注入IIocManager并使用IIocManager.IocContainer属性。若是在一个静态上下文或者不能注入IIocManager,做为最后的选择,可使用单例对象IocManager.Instance。可是,这样的话代码就不易测试了。
一些类须要在首次使用的时候初始化。IShouldInitialize有一个Initialize()方法。若是实现了它,当建立对象后(在使用前)Initialize()方法会自动调用。固然,能够注入或解析这个对象以便使用这个特征。
咱们必须调用依赖注入系统来解决在依赖图中的根对象。在ASP.NET MVC应用里,它一般为一个控制器类。咱们能够在控制器里使用构造函数和属性注入模式。当请求到达应用时,控制器使用IOC容器建立,全部的依赖递归解析。因此,谁作这些呢?这些由ABP经过扩展ASP.NET MVC的默认控制器工厂自动完成。一样,ASP.NET Web API也是这样的。不用关心建立和释放对象。
ASP.NET Core 已经有一个内置的依赖注入系统,在Microsoft.Extensions.DependencyInjection包里。ABP使用Castle.Windsor.MsDependencyInjection包集成和ASP.NET Core的依赖注入系统。因此,不须要关心它。
ABP简化且自动使用依赖入住,只要遵循规则并使用上述的结构。大多数时候不须要更多的依赖注入。可是,若是须要,能够直接使用Castle Windsor的力量执行任何任务(如自定义注册,注入钩子,拦截等等)。