ABP框架 - 依赖注入

时间:2023-01-26 13:56:14

文档目录

本节内容:

什么是依赖注入

如果你已经知道依赖注入概念、构造器和属性注入,你可以跳到下一主题。

*:“依赖注入是软件设计模式,一个依赖对象(或客户端)需要一个或多个依赖(或服务)的注入,或传入引用作为它状态的一部分。该模式把客户端的依赖的创建和客户端行为分离开来,这样使得程序设计更加地松藕合,更符合依赖倒置及单一职责原则。它与客户端了解依赖关系的服务定位器模式形成对比。”

如果不使用依赖注入技术,很难管理依赖并开发出一个模块和结构良好的应用。

传统方式的问题

在一个应用里,类之间相互依赖。假设我们有一个应用服务,它使用仓储插入实体到数据库,在这种情况下,应用服务类依赖于仓储类,如下:

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到数据库。这段代码的问题:

  • 该服务在CreatePerson方法里使用IPersonRepository的引用,所以这个方法依赖于IPersonRepository。虽然它替代了PersonRepository具体类,但该服务在它的构造器里仍然依赖于PersonRepository。组件应该依赖于接口而不依赖于实现,这是依赖倒置的原则。
  • 如果该服务自己创建PersonRepository,它就只能用于IPersonRepository的一个特定的实现里,就不能用于接口的其它实现里,因此从实现里分离接口就变得没有意义。硬依赖造成代码紧藉合和低重用性。
  • 在将来我们可能需要改变PersonRepository的创建,假设我们想把它变成单体(只用一个实例,而不是每处使用创建一个实例),或是想创建多个实现IPersonRepository类然后根据条件选择一个,在这种情况下,我们就需要修改所有依赖于IPersonRepository的类了。
  • 在这种依赖关系里,很难(甚至不可能)对该服务单元测试。

为克服这些问题,可以使用工厂模式。把仓储的创建抽象出来。代码如下:

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这个实现。但仍有些问题:

  • 此时,PersonAppService依赖于PersonRepositoryFactory,这稍微可以授受但还是存在硬依赖。
  • 为每个仓储或依赖写一个工厂/方法是很乏味的。
  • 它仍然难以测试,因为难以让PersonAppService使用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,首先创建一个IpersonRepository,然后把它传递给PersonAppService的构造器,如下所示:

var repository = new PersonRepository();
var personService = new PersonAppService(repository);
personService.CreatePerson("Yunus Emre", );

构造器注入是一个完美的方式,创建一个类独立于依赖对象的创建。但是,上述代码也有些问题:

  • 创建一个PersonAppService变得困难,考虑一下,有4个依赖,我们必须创建这样4个依赖对象,并把它们传入PersonAppService的构造器。
  • 依赖类可能有其它的依赖(此处,PersonRepository可能有其它依赖),所以我们必须创建所有PersonAppService的依赖,和依赖的依赖,如此下去...,这种情况下,我们甚至没办法创建一个单独的对象,因为依赖路径太复杂了。

幸运地是:有依赖注入框架能自动管理依赖。

属性注入模式

构造器注入为一个类的依赖的注入提供了一个完美的方式,这种方式你不能创建一个不提供依赖的类的实例,同样它是必须显式声明自身所有需要才能正确工作的强方式。

但是,在某些情况下,有些类依赖其它类,但也可以在不提供依赖的情况下工作,这在横切关注点(如日志)里经常遇到,一个类可以在没有日志的情况下工作,但当你提供一个日志记录器给它时,它也能写日志。在这种情况下,你可以定义一个公开的依赖属性,而不是构造器。考虑一下,我们想在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", );

假设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", );

我们首先创建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());

更多信息请参考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系统最适当的方式。

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<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 接口

有些类需要在第一次使用前初始化,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.aspnetboilerplate.com/Pages/Documents/Dependency-Injection

ABP框架 - 依赖注入的更多相关文章

  1. ABP的依赖注入

    目录 说说ABP的依赖注入 代码追踪 说说ABP的依赖注入 上篇abp运行机制分析分析了ABP在启动时,都做了那些事:这篇我们来说说ABP的最核心的一部分:依赖注入(DependencyInjecti ...

  2. ABP之依赖注入

    写在开头 ABP开源项目最近有点小火,还开展了线下活动.本着学习DDD的心态与学习开源代码的的好奇,我也看了一遍ABP源码,在此将自己学习ABP的一些心得记录下来. 作为核心的IoC 作为一种解耦的方 ...

  3. &lpar;译)ABP之依赖注入

    原文地址:https://aspnetboilerplate.com/Pages/Documents/Dependency-Injection 什么是依赖注入 传统方式的问题 解决方案 构造函数注入 ...

  4. Asp&period;Net Core 3&period;1 Api 集成Abp项目依赖注入

    Abp 框架 地址https://aspnetboilerplate.com/ 我们下面来看如何在自己的项目中集成abp的功能 我们新建core 3.1 API项目和一个core类库 然后 两个项目都 ...

  5. 04 Spring框架 依赖注入(一)

    整理了一下之前学习spring框架时候的一点笔记.如有错误欢迎指正,不喜勿喷. 上一节我们讲了几个bean的一些属性,用来限制我们实例创建过后的状态. 但是细心的我们会发现其实上面demo创建的实例并 ...

  6. 07 Spring框架 依赖注入(四)基于注解的依赖注入

    前面几节我们都在使用xml进行依赖的注入,但是在实际的开发中我们往往偏爱于使用注解进行依赖注入,因为这样更符合我们人的思维,并且更加快捷,本节就来讲述Spring基于注解的依赖注入: 信息注入注解 @ ...

  7. spring框架——依赖注入

    依赖注入:DI 又称控制反转:IoC 项目名字spring_DI 一.implement包中定义了两个接口Food和Person 1.接口Food package org.interfaces; pu ...

  8. 05 Spring框架 依赖注入(二)

    上一节我们讲了三种信息的注入,满足一个类的属性信息的注入,但是如果我们需要向一个实例中注入另一个实例呢?就像我们创建一个学生类,里边有:姓名,性别,年龄,成绩等几个属性(我习惯把类的域叫做属性),但是 ...

  9. 06 Spring框架 依赖注入(三)多配置文件

    在Spring前几节的学习中我们都使用了一个配置文件,就像struts2中可以包含其他的配置文件,我们能不能使用多个配置文件呢(在工程比庞大,配置比较多的时候)? Spring多配置文件分为两种: 平 ...

随机推荐

  1. 【HTML5&amp&semi;CSS3进阶03】Jser与Csser如何一起愉快的翻新老组件

    上次,我们形成了两种header的布局,一种flexbox,一种float,最后与身边做重构的同事交流下来,选择了float的布局. 事实上布局的选型不需要我关注,我的参与或者一些意见多数是自我提升, ...

  2. Android 基于Android的手机邮件收发(JavaMail)之二&lpar; Welcome&period;java 和 ReceiveAndSend&period;java &rpar;

    周末休息,这次我们继上次内容继续.上一篇内容我们讲述的是一些准备工作.下载两个javamail.jar和activation.jar文件,然后再BuildPath~ 言归正传,为了展示效果,在这里我申 ...

  3. 慕课网&lowbar;&lowbar;CSS&lowbar;&lowbar;line height

  4. struts的标签库出现Failed to load or instantiate TagExtraInfo class

    使用struts的标签库出现Failed to load or instantiate TagExtraInfo class 最近在使用struts标签库的时候,在eclipse开发环境中是正常的,放 ...

  5. grep -v 排除多人字符串

    # egrep -v '^$|^#' /etc/httpd/conf/httpd.conf # grep -v '^$\|^#' /etc/httpd/conf/httpd.conf

  6. sax解析案例&lpar;javabean封装xml文档数据&rpar;

    package itcast.sax; import java.io.IOException; import java.util.List; import javax.xml.parsers.Pars ...

  7. UIButton利用分类扩展方法(封装)

    UIButton+BackgroundColor.h #import <UIKit/UIKit.h> @interface UIButton (BackgroundColor) - (vo ...

  8. asterisk实时添加sip号码--sqlite篇

    原文:asterisk实时添加sip号码--sqlite篇 asterisk实时添加sip号码--sqlite篇 今天尝试用了asterisk的实时模式,往sqlite里面添加一个sip帐号,无需重启 ...

  9. C&num; 计算文件的HASH

    /// <summary> /// 提供用于计算指定文件哈希值的方法 /// <example>例如计算文件的MD5值: /// <code> /// String ...

  10. 基于&period;NET CORE微服务框架 -谈谈Cache中间件和缓存降级

    1.前言 surging受到不少.net同学的青睐,也提了不少问题,提的最多的是什么时候集成API 网关,在这里回答大家最近已经开始着手研发,应该在1,2个月内会有个初版API网关,其它像Token身 ...