ABP框架 - 依賴注入

文檔目錄html

 

本節內容:git

 

什麼是依賴注入github

若是你已經知道依賴注入概念、構造器和屬性注入,你能夠跳到下一主題。數據庫

維基百科:「依賴注入是軟件設計模式,一個依賴對象(或客戶端)須要一個或多個依賴(或服務)的注入,或傳入引用做爲它狀態的一部分。該模式把客戶端的依賴的建立和客戶端行爲分離開來,這樣使得程序設計更加地鬆藕合,更符合依賴倒置及單一職責原則。它與客戶端了解依賴關係的服務定位器模式造成對比。」設計模式

若是不使用依賴注入技術,很難管理依賴並開發出一個模塊和結構良好的應用。框架

 

傳統方式的問題單元測試

在一個應用裏,類之間相互依賴。假設咱們有一個應用服務,它使用倉儲插入實體到數據庫,在這種狀況下,應用服務類依賴於倉儲類,以下:測試

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到數據庫。這段代碼的問題:spa

  • 該服務在CreatePerson方法裏使用IPersonRepository的引用,因此這個方法依賴於IPersonRepository。雖然它替代了PersonRepository具體類,但該服務在它的構造器裏仍然依賴於PersonRepository。組件應該依賴於接口而不依賴於實現,這是依賴倒置的原則。
  • 若是該服務本身建立PersonRepository,它就只能用於IPersonRepository的一個特定的實現裏,就不能用於接口的其它實現裏,所以從實現裏分離接口就變得沒有意義。硬依賴形成代碼緊藉合和低重用性。
  • 在未來咱們可能須要改變PersonRepository的建立,假設咱們想把它變成單體(只用一個實例,而不是每處使用建立一個實例),或是想建立多個實現IPersonRepository類而後根據條件選擇一個,在這種狀況下,咱們就須要修改全部依賴於IPersonRepository的類了。
  • 在這種依賴關係裏,很難(甚至不可能)對該服務單元測試。

 爲克服這些問題,可使用工廠模式。把倉儲的建立抽象出來。代碼以下:.net

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

構造器注入是一個完美的方式,建立一個類獨立於依賴對象的建立。可是,上述代碼也有些問題:

  • 建立一個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", 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<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(),
        Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient()
    );

var personService = container.Resolve<IPersonAppService>();
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());

更多信息請參考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

相關文章
相關標籤/搜索