[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默认自动注册所有 RepositoriesDomain ServicesApplication 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样板开发框架) 分层架构

[Architect] ABP(现代ASP.NET样板开发框架) 依赖注入

ABP开发手记1 - 开篇

基于DDD的.NET开发框架 - ABP启动配置

基于DDD的.NET开发框架 - ABP依赖注入

基于DDD的.NET开发框架 - ABP Session实现