依赖注入
什么是依赖注入
如果你已经知道依赖注入,构造函数和属性注入模式,可以直接跳到下一部分。
维基百科说:"依赖注入是一种软件设计模式,一个或多个依赖项(或服务)被注入或通过引用传递到一个依赖对象,并且成为客户端状态的一部分。这种模式把客户端依赖项的创建从它自己的行为中分离出来,允许程序设计成松耦合的,遵循依赖倒置和单一职责的原则。和服务定位器模式相比,它允许客户端知道他们使用的系统查找依赖项。"
不使用依赖注入技术,很难管理依赖项和发布一个结构良好的应用。
传统方法的问题
在一个应用里,类之间相互依赖。假设我们有一个使用仓储插入实体到数据的应用服务,在这种情况下,这个应用服务类依赖仓储类。如下示例:
- public class PersonAppService
- {
- private IPersonRepository _personRepository;
- public PersonAppService()
- {
- _personRepository =new PersonRepository();
- }
- public voidCreatePerson(stringname,int age)
- {
- varperson =newPerson { Name = name, Age = age };
- _personRepository.Insert(person);
- }
- }
PersonAppService 使用 PersonRepository 向数据库中插入一个 Person 实体。这段代码有以下问题:
可以使用工厂模式克服这些问题中的一部分。因此,仓库类的创建应该是抽象的。请看如下代码:
- public class PersonAppService
- {
- private IPersonRepository _personRepository;
- public PersonAppService()
- {
- _personRepository = PersonRepositoryFactory.Create();
- }
- public voidCreatePerson(stringname,int age)
- {
- varperson =newPerson { 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 voidCreatePerson(stringname,int age)
- {
- varperson =newPerson { Name = name, Age = age };
- _personRepository.Insert(person);
- }
- }
这就是构造函数注入。现在,PersonAppService 不知道哪个类实现了 IPersonRepository,也不知道怎么创建它。想使用 PersonAPPService,首先创建一个 IPersonRepository,把它传递给 PersonAppService 的构造函数,如下所示:
- varrepository =new PersonRepository();
- varpersonService =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 对象后设置记录器,它就可以写日志了:
- varpersonService =newPersonAppService(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 里,就像如下所示:
- varcontainer =new WindsorContainer();
- container.Register(
- Component.For().ImplementedBy().LifestyleTransient(),
- Component.For().ImplementedBy().LifestyleTransient()
- );
- varpersonService = container.Resolve();
- personService.CreatePerson("Yunus Emre",19);
我们首先创建 WindsorContainer。然后使用接口注册 PersonRepository 和 PersonAppService。我们让容器创建一个 IPersonAppService。它创建 PersonAppService 及其依赖项并返回。在这个简单例子里,使用 DI 框架的优势并不是很明显,但是设想一下在一个真实的企业应用里将会有许多的类和依赖项。当然,注册依赖项只在应用启动的时候注册一次,创建和使用对象可能会在其他的地方。
注意,我们把对象的生命周期生命为短暂的。意味着无论什么时候我们需要类型的一个对象时,一个新的实例将会被创建。还有许多其他不同的生命周期(如单例)。
ABP 依赖注入基础设施
当你遵循最佳实践和一些约定编写应用的时候,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
可以使用 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();
- 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 是个示例类。它通过构造函数注入 IIcResolver 并且使用它解析和释放对象。有几个 Resolve 的重载方法可以在需要的时候使用。Release 方法用来释放组件(对象)。如果手动解析了一个对象,那么调用 Release 方法是很关键的。要不然,应用可能忽悠内存泄露的问题。为了确保释放对象,在任何可能的地方使用 ResolveAsDisposable(如上例所示)。它会在 using 块的结束为止自动调用 Release。
IIocResolver(和 IIocManager)有 CreateScope 扩展方法(定义在 Abp.Dependency 命名空间),用来安全释放所有解析的依赖对象。例如:
- using(varscope = _iocResolver.CreateScope())
- {
- varsimpleObj1 = scope.Resolve();
- varsimpleObj2 = scope.Resolve();
- //...}
在 using 块的末尾,所有解析的依赖对象自动移除。使用 IScopedIocResolver, 作用范围也是可以注入的。可以注入这个接口用来解析所有依赖项。当类被释放的时候,所有解析的依赖项也会被释放。但是,需要小心使用;例如,如果有个类有很长的生命(比方是单例)并且解析了大量的对象,所有的这些类都会存在内存中,直到这个类被释放。
如果想直接使用 IOC 容器(Castle Windsor)解析依赖项,可以使用构造函数注入 IIocManager 并使用 IIocManager.IocContainer 属性。如果在一个静态上下文或者不能注入 IIocManager,作为最后的选择,可以使用单例对象 IocManager.Instance。但是,这样的话代码就不易测试了。
额外部分
IShouldInitialize 接口
一些类需要在首次使用的时候初始化。IShouldInitialize 有一个 Initialize() 方法。如果实现了它,当创建对象后(在使用前)Initialize() 方法会自动调用。当然,可以注入或解析这个对象以便使用这个特征。
ASP.NET MVC 和 ASP.NET Web API 集成
我们必须调用依赖注入系统来解决在依赖图中的根对象。在 ASP.NET MVC 应用里,它通常为一个控制器类。我们可以在控制器里使用构造函数和属性注入模式。当请求到达应用时,控制器使用 IOC 容器创建,所有的依赖递归解析。所以,谁做这些呢?这些由 ABP 通过扩展 ASP.NET MVC 的默认控制器工厂自动完成。同样,ASP.NET Web API 也是这样的。不用关心创建和释放对象。
ASP.NET Core 集成
ASP.NET Core 已经有一个内置的依赖注入系统,在 Microsoft.Extensions.DependencyInjection 包里。ABP 使用 Castle.Windsor.MsDependencyInjection 包集成和 ASP.NET Core 的依赖注入系统。所以,不需要关心它。
最后建议
ABP 简化且自动使用依赖入住,只要遵循规则并使用上述的结构。大多数时候不需要更多的依赖注入。但是,如果需要,可以直接使用 Castle Windsor 的力量执行任何任务(如自定义注册,注入钩子,拦截等等)。
返回主目录
来源: http://www.cnblogs.com/xajh/p/6753781.html