本节内容:
什么是依赖注入
如果你已经知道依赖注入概念、构造器和属性注入,你可以跳到下一主题。
维基百科:"依赖注入是软件设计模式,一个依赖对象(或客户端)需要一个或多个依赖(或服务)的注入,或传入引用作为它状态的一部分。该模式把客户端的依赖的创建和客户端行为分离开来,这样使得程序设计更加地松藕合,更符合依赖倒置及单一职责原则。它与客户端了解依赖关系的服务定位器模式形成对比。"
如果不使用依赖注入技术,很难管理依赖并开发出一个模块和结构良好的应用。
传统方式的问题
在一个应用里,类之间相互依赖。假设我们有一个应用服务,它使用仓储插入实体到数据库,在这种情况下,应用服务类依赖于仓储类,如下:
- 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 到数据库。这段代码的问题:
为克服这些问题,可以使用工厂模式。把仓储的创建抽象出来。代码如下:
- 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().ImplementedBy().LifestyleTransient(), Component.For().ImplementedBy().LifestyleTransient());
- var personService = container.Resolve();
- personService.CreatePerson("Yunus Emre", 19);
我们首先创建 WindsorContainer 容器,接着用它们的接口注册 PersonRepository 和 PersonAppService,然后我们要求容器创建一个 IPersonAppService,它就会用依赖创建 PersonAppService 并返回。在这个简单的示例里使用 DI 框架,可能不能明显得看出好处来,但是考虑一下,如果你在一个真实的企业应用遇到很多类和依赖,此时情况就不同了。当然,可以在使用前的其它地方注册依赖,也可以在一个应用启动时只注册一次。
注册我们同时把对象生命周期(life cycle)声明为短暂的(transient),这就意味着,当我们解析这个类型的对象时,就会创建一个新的实例。还有一些其它不同的生命周期(如单例)。
ABP 依赖注入基础
当你按照最佳实践和一些约定写你的应用时,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 注册依赖(一般在你的类的预初始化里):
- IocManager.Register < IMyService,
- MyService > (DependencyLifeStyle.Transient);
使用 Castle Windsor API
你可以使用 IIocManger.IocContainer 属性访问 Castle Windsor 容器并注册依赖。例如:
- IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn < IMySpecialInterface > ().LifestylePerThread().WithServiceSelf());
更多信息请参考。
解析
在你的应用某个需要使用 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 系统最适当的方式。
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();
- personService1.CreatePerson(new CreatePersonInput {
- Name = "Yunus",
- Surname = "Emre"
- });
- _iocResolver.Release(personService1);
- //Resolving and using in a safe way
- using(var personService2 = _iocResolver.ResolveAsDisposable()) {
- 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 接口
有些类需要在第一次使用前初始化,IShouldInitialize 有一个 Initialize 方法,如果你实现了它,那么在创建你的对象之后(使用之前)就会自动调用你的 Initialize 方法。当然,你应该注入 / 解析这个对象,以便这一特性起作用。
Asp.net Mvc 和 Asp.net Web Api 集成
我们必须调用依赖注入系统来解析依赖图上的根对象。在一个 Asp.net Mvc 应用里,它通常是一个 Controller(控制器)类。我们同样也可以在控制器里用构造器注入和属性注入模式。当一个请求到达我们的应用,用 IOC 容器创建控制器和所有依赖递归解析。所以由谁来做这件事?由 ABP 通过扩展 Mvc 的默认控制器工厂自动完成。类似地,Asp.net Web Api 也一样。而且你也不必关系创建和销毁对象。
Asp.net Core 集成
暂略
最后提醒
只要你依照上面的规则和结构,ABP 简化和自动使用依赖注入。大部分情况你不用做更多的事,但是只要你需要,你可以直接使用 Castle Windsor 的所有能力来执行任何任务(像自定义注册,注入钩子,拦截器等等)。
来源: http://www.cnblogs.com/kid1412/p/5980068.html