ABP(現代ASP.NET樣板開發框架)系列之六、ABP依賴注入

點這裏進入ABP系列文章總目錄html

 

基於DDD的現代ASP.NET開發框架--ABP系列之六、ABP依賴注入
git

 

ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。github

ABP的官方網站http://www.aspnetboilerplate.com數據庫

ABP在Github上的開源項目https://github.com/aspnetboilerplate設計模式

 


 
本文由 上海-半冷 提供翻譯
  架構

什麼是依賴注入

若是你已經知道依賴注入的概念,構造函數和屬性注入模式,你能夠跳過這一節。 框架

維基百科說:「依賴注入是一種軟件設計模式的一個或多個依賴項注入(或服務),或經過引用傳遞,爲依賴對象(或客戶)和客戶端狀態的一部分。模式之間創建一個客戶的依賴關係的行爲,它容許程序設計是鬆散耦合的,依賴倒置和單一職責原則。它直接對比service locator模式,它容許客戶瞭解他們所使用的系統找到依賴。」。 模塊化

若是不使用依賴注入技術,很難進行依賴管理、模塊化開發和應用程序模塊化。 函數

傳統方式的問題

在一個應用程序中,類之間相互依賴。假設咱們有一個應用程序服務,使用倉儲(repository)類插入實體到數據庫。在這種狀況下,應用程序服務類依賴於倉儲(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插入Person到數據庫。這段代碼的問題:

  • PersonAppService經過IPersonRepository調用CreatePerson方法,因此這方法依賴於IPersonRepository,代替了PersonRepository具體類。但PersonAppService(的構造函數)仍然依賴於PersonRepository。組件應該依賴於接口而不是實現。這就是所謂的依賴性倒置原則。
  • 若是PersonAppService建立PersonRepository自己,它成爲依賴IPersonRepository接口的具體實現,不能使用另外一個實現。所以,此方式的將接口與實現分離變得毫無心義。硬依賴(hard-dependency)使得代碼緊密耦合和較低的可重用。
  • 咱們可能須要在將來改變建立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的實現的對象,這個對象取決於PersonRepositoryFactory的Create方法。可是,仍然存在一些問題:

  • 此時,PersonAppService取決於PersonRepositoryFactory。這是更容易接受,但仍有一個硬依賴(hard-dependency)。
  • 爲每一個庫或每一個依賴項乏味的寫一個工廠類/方法。
  • 測試性依然很差,因爲很難使得PersonAppService使用mock實現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個依賴,咱們必須建立這四個依賴對象,並將它們傳遞到構造函數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實例設置了Logger,以下面:

    var personService = new PersonAppService(new PersonRepository());
    personService.Logger = new Log4NetLogger();
    personService.CreatePerson("Yunus Emre", 19);

假設Log4NetLogger實現ILogger實例,使得咱們可使用Log4Net庫寫日誌。所以,PersonAppService能夠寫日誌。若是咱們不設置Logger,PersonAppService就不寫日誌。所以,咱們能夠說PersonAppService ILogger實例是一個可選的依賴。

幾乎全部的依賴注入框架都支持屬性注入模式

依賴注入框架

有許多依賴注入框架,均可以自動解決依賴關係。他們能夠建立全部依賴項(遞歸地依賴和依賴關係)。因此你只須要根據注入模式寫類和類構造函數&屬性,其餘的交給DI框架處理!在良好的應用程序中,類甚至獨立於DI框架。整個應用程序只會有幾行代碼或類,顯示的與DI框架交互。

ABP的依賴注入基於 Castle Windsor框架。Castle Windsor最成熟的DI框架之一。還有不少這樣的框架,如Unity,Ninject,StructureMap,Autofac等等。

在使用一個依賴注入框架時,首先註冊您的接口/類到依賴注入框架中,而後你就能夠resolve一個對象。在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框架也許不是那麼簡潔,但想象下,在實際的企業應用程序中你會有不少類和依賴關係。固然,註冊的依賴項只在程序啓動的某個地方建立一次。

請注意,咱們只是講對象聲明爲臨時對象(transient)。這意味着每當咱們建立這些類型的一個對象時,就會建立一個新的實例。有許多不一樣的生命週期(如Singletion)。

ABP依賴注入的基礎結構

在編寫應用程序時遵循最佳實踐和一些約定,ABP幾乎讓依賴注入框架使用變得無形。

註冊

在ABP中,有不少種不一樣的方法來註冊你的類到依賴注入系統。大部分時間,常規方法就足夠了。

常規註冊

按照約定,ABP自動註冊全部 Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。例如,您可能有一個IPersonAppService 接口和實現類PersonAppService:

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

ABP會自動註冊它,由於它實現IApplicationService接口(它只是一個空的接口)。它會被註冊爲transient (每次使用都建立實例)。當你注入(使用構造函數注入)IPersonAppService接口成一個類,PersonAppService對象會被自動建立並傳遞給構造函數。

命名約定在這裏很是重要。例如你能夠將名字PersonAppService改成 MyPersonAppService或另外一個包含「PersonAppService」後綴的名稱,因爲IPersonAppService包含這個後綴。可是你能夠不遵循PeopleService命名您的服務類。若是你這樣作,它將不會爲IPersonAppService自動註冊(它須要自注冊(self-registration)到DI框架,而不是接口),因此,若是你想要你應該手動註冊它。

ABP按照約定註冊程序集。因此,你應該告訴ABP按照約定註冊您的程序集。這很容易:

    IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());

Assembly.GetExecutingAssembly()獲得一個對包括此代碼的程序集的引用。你能夠經過RegisterAssemblyByConvention方法註冊其餘程序集。這同在你的模塊初始化(AbpModule.Initialize())時完成。請查看ABP的模塊系統得到更多信息。

您能夠經過實現IConventionalRegisterer接口和調用IocManager。AddConventionalRegisterer方法編寫本身的約定註冊類。你應該將它添加到模塊的pre-initialize方法中。

幫助接口

你能夠註冊一個特定的類,不遵循傳統的約定製度規則。ABP提供了ITransientDependency和ISingletonDependency接口的快捷方法。例如:

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

以這種方式,您能夠很容易地註冊MyPersonManager爲transient。當須要注入IPersonManager時,MyPersonManager會被使用。注意,依賴被聲明爲單例。所以,建立的MyPersonManager同一個對象被傳遞給全部須要的類。只是在第一次使用時建立,那麼應用程序的整生命週期使用的是同一實例。

自定義/直接 註冊

若是以前描述的方法仍是不足以應對你的狀況,你可使用Castle Windsor註冊類和及依賴項。所以,您將擁有Castle Windsor註冊的全部能力。

能夠實現IWindsorInstaller接口進行註冊。您能夠在應用程序中建立一個實現IWindsorInstaller接口的類:

    public class MyInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
        }
    }

Abp自動發現和執行這個類。最後,你能夠經過使用IIocManager.IocContainer屬性獲得WindsorContainer。有關更多信息,閱讀Windsor的文檔。

解析(Resolving)

註冊通知IOC(控制反轉)容器關於你的類,它們的依賴項和生命週期。在您的應用程序須要使用IOC容器建立對象時,ASP.NET提供了一些方法解決依賴關係。

構造函數 & 屬性注入

做爲最佳實踐,你可使用構造函數和屬性注入去獲取你的類的依賴。任何可能的地方,你都應該這樣作。例子:

    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是一個應用程序的示例類。IIcResolver經過構造函數注入,而後用它來建立和釋放對象。有幾個解決方法的重載能夠根據須要使用。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應用程序,一般是一個控制器類。咱們可使用構造函數注入模式注入控制器。當一個請求來到咱們的應用程序中,控制器和全部依賴項被IOC容器遞歸建立。因此,誰作了這些?這是被Abp擴展的ASP.NET MVC默認控制器工廠自動完成的。ASP.NET Web API 也是類似的。你不用關心對象的建立和釋放。

最後說明

Abp簡化並自動使用依賴注入,只要你遵照規則和使用上面的結構。大多數時候這樣就夠了。可是若是不能知足你的需求,你能夠直接使用Castle Windsor的全部能力來執行任何任務(如自定義註冊,注入鉤子,攔截器等等)。

 

 


 

但願更多國內的架構師能關注到ABP這個項目,也許這其中有能幫助到您的地方,也許有您的參與,這個項目能夠發展得更好。

歡迎加QQ羣:

ABP架構設計交流羣:134710707 ABP架構設計交流羣      ABP架構設計交流2羣: 579765441ABP架構設計交流羣2

 

點這裏進入ABP系列文章總目錄

相關文章
相關標籤/搜索