[Architect] ABP(现代ASP.NET样板开发框架) 依赖注入
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Architect] ABP(现代ASP.NET样板开发框架) 依赖注入相关的知识,希望对你有一定的参考价值。
本节目录:
- 什么是依赖
- 传统方式的问题
- 解决方案
- 构造函数注入
- 属性注入
- 注入框架
- Abp依赖注入框架
- 注册
- 通常注册
- 帮助接口
- 自定义注册
- 解析
- 构造函数 & 属性注入
- IIocResolver & IIocManager
- 扩展
- IShouldInitialize
- ASP.NET MVC & ASP.NET Web API注入
- 注册
什么是依赖
如果你已经知道依赖注入思想,构造函数和属性注入模式,你可以跳到下节.
维基:"依赖注入是1种软件设计模式,在这种模式下,1个或多个依赖或服务被注入或者通过引用传递到调用端,成为调用端的一部分.这种模式将创建依赖从自己的行为中分离出来,这遵循松耦合的程序设计并遵循依赖反转和单一职责原则."
如果不使用依赖注入技术,管理依赖和开发1个结构化的模块非常麻烦.
传统方式的问题
在程序中,类相互依赖.假如有1个application service,使用 repository to insert entities to database.在这种情况下,该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 插入1个 Person 数据到数据库中. 有以下几个问题:
- 在CreatePerson 方法中,PersonAppService 使用IPersonRepository ,所以该方法依赖IPersonRepository.而不是具体的PersonRepository.但是PersonAppService 在构造函数中,依赖PersonRepository .组件应该依赖接口而不是具体实现.这是依赖倒置原则.
- 如果PersonAppService 直接创建PersonRepository ,则变成直接依赖IPersonRepository 1个特殊的实现.并且不能再其他的实现下工作.那么从实现中分离接口变的没有意义.高度依赖会使代码紧耦合\低重用.
- 我们希望改成创建PersonRepository 的方式.也就是我们需要将它变成单例,或者我们可能想创建IPersonRepository 其他的实现.选择其中1个创建.在这种情况下,我们需要在所有依赖IPersonRepository类中改变.
- 在这个依赖下,将很难做PersonAppService的单元测试.
使用factory模式可以解决一些问题.创建的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 是1个静态类,可以创建返回1个IPersonRepository.这就是Service Locator模式.他解决PersonAppService 不知道如何创建实现问题,而且不依赖PersonRepository 实现.但是:
- 此时,PersonAppService 依赖PersonRepositoryFactory.虽然更容易接受了,但是仍然是强依赖.
- 需要重复的为每个repository写factory class/method .
- 还是不太方便测试,很难使用mock implementation 作为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,先创建1个IPersonRepository 传递到构造函数中.如:
var repository = new PersonRepository(); var personService = new PersonAppService(repository); personService.CreatePerson("Yunus Emre", 19);
构造函数注入是一种不依赖对象的完美方式.但是,也有一些问题.
- 创建1个PersonAppService 变的麻烦.如果有4个依赖,完美需要创建4个依赖对象传递到PersonAppService的构造函数中.
- 依赖的类可能会有其他的依赖.所以,我们需要创建PersonAppService所有的依赖.以及依赖的所有依赖.如果依赖太复杂,甚至我们无法创建1个对象.
幸好,这里有一些自动管理依赖的框架: Dependency Injection frameworks
属性注入模式
构造函数模式是创建依赖的完美方式.它也强制定义了需要哪些类才能工作.
但是,有些情况下,类依赖的类没有提供也能运行.比如logging.1个类可以不需要logging工作,但是你提供了logger,应该也能写日志.在这种情况下,你需要定义依赖作为public 属性更好.如果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是1个实现了ILogger空对象的单例.所以,PersonAppService 可以在设置了Logger后写日志.
var personService = new PersonAppService(new PersonRepository()); personService.Logger = new Log4NetLogger(); personService.CreatePerson("Yunus Emre", 19);
如果Log4NetLogger 实现ILogger 接口,则会调用Log4Net 类库写日志.因此,PersonAppService 则可以写日志.如果我们不设置Logger,则不会写日志.所以,我们可以说ILogger是PersonAppService可选的依赖项.
大部分依赖注入框架都提供了属性注入模式.
依赖注入框架
有很多自动解析依赖的框架.他们可以创建对象,并创建对象的所有依赖.所以,你只需要通过构造函数和属性注入的模式写class.依赖注入框架负责其他!.在一个好的程序中,你的classes甚至不依赖于DI框架.在你的程序中,只有几行代码和classes,就可以和DI框架做交互.
Abp使用 Castle Windsor 框架作为依赖注入.他是流行的DI框架之一.还有其他的,如:Unity, Ninject, StructureMap, Autofac 等等.
在1个DI框架下,首先你需要注册interfaces/classes到DI框架中,然后你可以解析创建对象.在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 以及他们的接口.然后我们让container 创建1个IPersonAppService.他会创建PersonAppService 对象.在这个例子中,也许不够清楚的提现优势.但是在1个真实的企业应用中,会有很多应用和依赖.当然,在程序启动的时候创建一次注册依赖,在其他的地方创建和使用对象.
Abp依赖注入框架
在Abp中,几乎无形的使用着DI框架,当你写程序时,遵循惯例的时候.
注册
在Abp中,有不同的方式注册你的类到DI系统中,大多数情况下,安装常规注册即可.
惯例注册
Abp默认自动注册所有 Repositories, Domain Services, Application Services,MVC Controllers and Web API Controllers.如,你有1个IPersonAppService 接口和1个PersonAppService 实现它.
public interface IPersonAppService : IApplicationService { //... } public class PersonAppService : IPersonAppService { //... }
Abp注册实现了IApplicationService 接口的类.注册类型为transient (每次使用创建1个实例).当你注入1个IPersonAppService 接口到1个类中,则会创建PersonAppService 对象并自动传入.
Naming conventions 非常重要.如你可以改变PersonAppService的名字为MyPersonAppService 或其他含有‘PersonAppService‘尾缀的名字.因为IPersonAppService 有这个尾缀.但是你不可以改为PeopleService.否则,他会以自身的方式注册,而不是IPersonAppService 接口.所以,如果你需要,则需手工注册.
Abp可以按照惯例注册程序集.所以,你需要告诉Abp去注册你的程序集.如:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Assembly.GetExecutingAssembly() 会获得1个包含此代码的程序集.你可以传入其他的assemblies 到RegisterAssemblyByConvention 方法.通常在你的module initialized方法中处理.
你可以自定义实现IConventionalRegisterer 接口来注册类.并通过你的class调用IocManager.AddConventionalRegisterer 方法.你需要在模块的pre-initialize方法中添加.
帮助接口
你可能想注册1个特别的类.Abp提供了ITransientDependency andISingletonDependency 2个接口.如:
public interface IPersonManager { //... } public class MyPersonManager : IPersonManager, ISingletonDependency { //... }
在这种情况下,你可以轻松注册MyPersonManager.当需要注入IPersonManager时,会创建MyPersonManager .通知DI框架为单例方式.然后1个单例的对象创建并在其他的类中也使用这个对象.只会在第一次使用的时候创建,然后整个软件生命周期中会一直使用相同的对象.
自定义注册
你也可以直接使用 Castle Windsor 来注册你的类和依赖.然后,你可以在Castle windsor中注册所有的能力.
Castle Windsor 有IWindsorInstaller 接口,可以实现注册功能.你可以创建1个类实现IWindsorInstaller 接口
public class MyInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf()); } }
Abp自动找到并执行这个类.最后,你可以通过属性 IIocManager.IocContainer 来获得WindsorContainer .
解析
注册会把 classes, their dependencies and lifecycles通知给IoC容器.在程序中创建对象的时候使用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
有时,你可能需要直接解析依赖,而不是通过constructor & property injection.需要尽量避免这种情况,但是也有可能.Abp提供了一些service注射和使用.如:
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" }); } } }
通过构造注入IIcResolver .它有一些Resolve方法的重载.Resolve方法用于解析对象.Release方法用于是否对象.如果你手工解析1个对象,释放对象是很关键的.否则,你的程序会有内存溢出问题.为了确保释放对象,使用ResolveAsDisposable ,会在using语句结束时,自动调用Release方法.
如果你想IOC Container来解析依赖,你可以通过构造函数注入IIocManager ,并使用属性IIocManager.IocContainer .如果你在1个静态的上下文中,将无法注入IIocManager.最后的办法,你可以在任何地方使用单例对象 IocManager.Instance .但是这样,你的代码很难测试.
扩展
IShouldInitialize 接口
一些类在使用前需要初始化.IShouldInitialize 接口提供方法Initialize.如果你实现他,你的Initialize方法会在创建对象自动调用.当然,你需要注入解析对象才能让它起作用.
ASP.NET MVC & ASP.NET Web API integration
当然,我们需要依赖注入系统解析根对象.在ASP.NET MVC中,通常是Controller.我们在Controller中可以使用构造函数注入和属性注入.当1个请求到达系统时,会由IOC解析controller和所有的依赖.这一切由Abp继承MVC默认的controller factory自动完成.在 ASP.NET Web API 中也是类似如此.你不需要关心如何创建和释放对象.
当你遵循规则的时候,将很容易使Abp使用依赖注入自动完成这些.Castle Windsor也提供了自定义方式用来扩展你的操作.(自定义注册方式,注入构造,拦截器等等).
以上是关于[Architect] ABP(现代ASP.NET样板开发框架) 依赖注入的主要内容,如果未能解决你的问题,请参考以下文章
[Architect] ABP(现代ASP.NET样板开发框架) 分层架构