ABP之依賴注入

維基百科中說「依賴注入是一種軟件設計模式,在這種模式中,一種或多種依賴項(或服務)被注入,或者經過引用傳遞到一個依賴對象(或客戶端),併成爲客戶端狀態的一部分,這種模式將客戶端依賴的建立和其自身行爲分離開來,容許程序設計鬆散耦合,
遵循依賴注入和單一責任原則。它直接對比了服務定位模式,該模式容許客戶瞭解其用來查找依賴項的系統」。git

在不使用依賴注入技術的狀況下,很難去管理依賴項和去開發一個模塊化而且結構良好的應用程序。github

傳統方法中存在的問題

在應用程序中,類與類之間是互相依賴,假設咱們如今有一個使用倉儲庫將實體插入數據庫的應用程序服務。這種情形下,應用程序服務類依賴於倉儲庫類,看下面的例子:web

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往數據庫中插入了Person,雖然看起來也沒什麼害處,可是這段代碼仍是有一些問題:數據庫

  • PersonAppService對CreatePerson方法使用IPersonRepository引用。該方法依賴於IPersonRepository接口,而不是PersonRepository具體類。然而,對於構造函數,PersonAppService依賴於PersonRepository而不是接口。組件應該依賴於接口,而不是具體的實現。這就是所謂的依賴倒置原理。
  • 若是PersonAppService建立了PersonRepository自己,它就依賴於IPersonRepository接口的特定實現。這不能與其餘實現一塊兒工做。所以,將接口與實現分離變得毫無心義。硬依賴關係使代碼基礎緊密耦合,從而忽視了可重用性。在這種狀況下,咱們必須更改全部依賴於IPersonRepository的類。
  • 有了這樣的依賴關係,對PersonAppService進行單元測試很是困難(或不可能)。

爲了克服上述問題,可使用工廠模式,倉儲類的建立時抽象的,以下所示:設計模式

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 的實現是獨立的。可是仍然有一些問題:安全

PersonRepositoryFactory是一個靜態類,它建立並返回IPersonRepository.這稱爲服務器定位模式。建立問題被解決了,由於PersonAppService 不知道IPersonRepository的建立是怎麼實現的,而且它和PersonRepository 的實現是獨立的。可是仍然有一些問題:服務器

  • 這個時候,PersonAppService 依賴於PersonRepositoryFactory,這個更容易被接收,可是仍然是一個硬依賴
  • 爲每一個存儲庫或每一個依賴項編寫工廠類/方法是很乏味的。
  • 一樣,這也不容易測試,由於很難讓PersonAppService使用IPersonRepository的模擬實現。

解決方案

有一些最佳實踐(模式)能夠幫助咱們依賴於其餘類:app

構造方法注入模式

上面的例子能夠重寫以下:框架

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("John Doe", 32);

構造函數注入是使類獨立於依賴對象的建立的一種很好的方法,可是上面的代碼有一些問題:

  • 建立一個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對象以後設置了Logger屬性,那麼PersonAppService 能夠寫日誌:

var personService = new PersonAppService(new PersonRepository());
personService.Logger = new Log4NetLogger();
personService.CreatePerson("John Doe", 32);

假設Log4NetLogger實現了ILogge,而且它使用Log4Net 庫寫日誌,這樣PersonAppService 能夠真正的寫日誌。若是咱們沒有設置Logger ,它不會寫日誌。咱們能夠說ILogger是PersonAppService的一個可選擇的依賴。
大多數依賴注入框架都支持屬性注入模式。

依賴注入框架

有許多依賴注入框架能夠自動解決依賴項。他們能夠遞歸的建立全部依賴項,以及依賴項的依賴項的對象。

僅僅是簡單的使用構造函數和屬性注入模式編寫類,依賴注入框架會作剩下的事情,在一個好的應用程序中,你的類是獨立的,甚至獨立於依賴注入框架。在整個應用程序中,只有幾行代碼或類顯式地與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("John Doe", 32);

首先,咱們建立了WindsorContainer,而且使用其接口註冊了PersonRepository 和 PersonAppService。咱們使用這個容器窗口了IPersonAppService。它用依賴關係建立了具體的類PersonAppService,而後返回它。在這個簡單的例子中,可能使用DI框架的優點不明顯。然而在一個真正的企業項目中,你可能有多個類和依賴項。依賴項的註冊和對象的使用時分開的,而且在應用程序啓動時只建立一次。

注意,咱們還將對象的生命週期設置爲瞬態的。這意味着不管什麼時候解析這些類型的對象,都會建立一個新實例。有許多不一樣的生命週期,例如單例。

ABP的依賴注入組件

ABP使得對依賴注入框架的使用幾乎是不可見的。它還經過遵循最佳實踐和約定來幫助咱們寫應用程序。

註冊依賴

在ABP有許多方法能夠將類註冊到依賴注入系統,大多數狀況下,傳統的註冊就足夠了。

傳統的註冊

 ABP根據約定自動註冊全部的 Repositories, Domain Services, Application Services, MVC Controllers and Web API Controllers,例如,這裏有一個IPersonAppService接口和一個實現它的PersonAppService類:

 public interface IPersonAppService : IApplicationService
    {
        //...
    }

    public class PersonAppService : IPersonAppService
    {
        //...
    }

ABP自動註冊它,由於它實現了IApplicationService接口(它只是一個空接口)。它被註冊爲transient瞬態,也就是說每次使用都會建立它。當咱們將IPersonAppService接口(使用構造函數注入)注入到類中時,將自動建立一個PersonAppService對象並將其傳遞到構造函數中。

這裏的命名約定很是重要。例如,您能夠將PersonAppService的名稱更改成MyPersonAppService或包含「PersonAppService」後綴的其餘名稱。這將它註冊到IPersonAppService,由於它有相同的後綴。可是,若是不能沒有後綴,好比「PeopleService。若是您這樣作,它不會自動註冊到IPersonAppService。相反,它使用自注冊(而不是接口)註冊到DI框架。在這種狀況下,您能夠手動註冊它。

ABP能夠按照約定註冊程序集:

IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

Assembly.GetExecutingAssembly() 獲取對包含此代碼的程序集的引用。咱們能夠將其餘程序集傳遞給RegisterAssemblyByConvention方法。這一般是在初始化模塊時完成的。

咱們能夠經過實現IConventionalRegisterer接口,而後調用IocManager.AddConventionalRegisterer方法來編寫本身的常規註冊類。咱們應該將其添加到模塊的預初始化方法中。

輔助接口Helper Interfaces

您可能想註冊一個不符合常規註冊規則的特定類。ABP提供ITransientDependency、IPerWebRequestDependency和ISingletonDependency接口做爲快捷方式。例如:

public interface IPersonManager
{
    //...
}

public class MyPersonManager : IPersonManager, ISingletonDependency
{
    //...
}

經過這種方式,WOMEN 能夠輕鬆註冊MyPersonManager。當須要注入IPersonManager時,將使用MyPersonManager類。注意,依賴項聲明爲單例。建立MyPersonManager的一個實例,並將相同的對象傳遞給全部須要的類。它在第一次使用時實例化,而後在應用程序的整個生命週期中使用。

注意:IPerWebRequestDependency只能在web層中使用。

Custom/Direct Registration

若是傳統的註冊不能知足您的須要,您可使用IocManager或Castle Windsor來註冊您的類和依賴項

使用IocManager

你可使用IocManager來註冊依賴項(一般在模塊定義類的預初始化方法中):

IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);

使用Castle Windsor API

 

您可使用IocManager.IocContainer屬性訪問Castle Windsor容器並註冊依賴項。例子:

IocManager.IocContainer.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());

解決依賴關係

註冊通知IOC(控制反轉)容器(也稱爲DI框架)關於類、它們的依賴關係和生命週期。在應用程序的某個地方,您須要使用IOC容器建立對象。ASPNET提供了一些解決依賴關係的選項。

構造函數和屬性注入

做爲最佳實踐,咱們應該使用構造函數和屬性注入來得到類的依賴項。例子:

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<PersonAppService>();
        personService1.CreatePerson(new CreatePersonInput { Name = "John", Surname = "Doe" });
        _iocResolver.Release(personService1);

        //Resolving and using in a safe way
        using (var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>())
        {
            personService2.Object.CreatePerson(new CreatePersonInput { Name = "John", Surname = "Doe" });
        }
    }
}

MySampleClass是應用程序示例類。它被構造器注入IIocResolver,並使用它解析和釋放對象。解析方法有一些重載,能夠根據須要使用。Release方法用於釋放組件(對象)。若是咱們手工解析一個對象,調用Release是很是重要的。不然,應用程序可能有內存泄漏。爲了確保釋放對象,儘量使用ResolveAsDisposable(如上面的示例所示)。Release在using塊的末尾自動調用。

IIocResolver(和IIocManager)還具備CreateScope擴展方法(在app. dependency名稱空間中定義)來安全地釋放全部已解析的依賴項。

using (var scope = _iocResolver.CreateScope())
{
    var simpleObj1 = scope.Resolve<SimpleService1>();
    var simpleObj2 = scope.Resolve<SimpleService2>();
    //...
}

在using塊結束處,全部已解析的依賴項將自動刪除。還可使用IScopedIocResolver注入scope。咱們能夠注入這個接口並解決依賴關係。當釋放類時,全部已解析的依賴項都將被釋放。使用這個仔細!若是咱們的類有很長的生命週期(假設它是一個單例),而且咱們解析了太多的對象,那麼全部的對象都將保留在內存中,直到咱們的類被釋放。

若是咱們想直接訪問IOC容器(Castle Windsor)來解決依賴關係,咱們能夠構造-注入IIocManager並使用IIocManager.IocContainer屬性。若是咱們在靜態上下文中或者不能注入IIocManager,做爲最後的手段,可使用單例對象IocManager。實例無處不在。然而,在這種狀況下,代碼並不容易測試。

擴展知識

IShouldInitialize接口

有些類在第一次使用以前須要初始化。IShouldInitialize有一個Initialize()方法。若是實現了它,那麼在建立對象以後(在使用對象以前)會自動調用Initialize()方法。須要注入/解析對象才能使用該特性。

ASP.NET MVC和ASP.NET Web API集成

咱們必須調用依賴注入系統來解析依賴關係圖中的根對象。在ASPNETMVC應用程序中,它一般是一個控制器類。咱們還能夠在控制器中使用構造器和屬性注入模式。當請求到達應用程序時,使用IOC容器建立控制器,並遞歸地解析全部依賴項。這是怎麼發生的?這一切都是由ABP經過擴展ASP.NET MVC的默認控制器工廠自動完成的。對於ASP.NETWeb API也是如此。咱們沒必要擔憂建立和處理對象。

ASP.NET Core 集成

ASPNETCore已經有一個內置的依賴注入系統,帶有Microsoft.Extensions.DependencyInjection包.ABP使用Castle.Windsor.MsDependencyInjection包將其依賴注入系統集成到ASP.Net Core.、

最後:

只要遵循規則並使用上面的結構,ABP就能夠簡化和自動化依賴項注入。大多數時候不須要咱們作什麼。若是須要,能夠直接使用Castle Windsor的原始功能執行許多任務,好比定製註冊、注入掛鉤、攔截器等等。

相關文章
相關標籤/搜索