<<ABP框架>> 依賴註入

来源:http://www.cnblogs.com/kid1412/archive/2016/10/22/5980068.html
-Advertisement-
Play Games

文檔目錄 本節內容: 什麼時依賴註入 傳統方式的問題 解決方案 構造器註入模式 屬性註入模式 依賴註入框架 ABP 依賴註入基礎 註冊依賴 約定註入 輔助介面 自定義/直接 註冊 使用IocManager 使用Castle Windsor API 解析 構造器和屬性註入 IIocResolver 和 ...


文檔目錄

 

本節內容:

 

什麼是依賴註入

如果你已經知道依賴註入概念、構造器和屬性註入,你可以跳到下一主題。

維基百科:“依賴註入是軟體設計模式,一個依賴對象(或客戶端)需要一個或多個依賴(或服務)的註入,或傳入引用作為它狀態的一部分。該模式把客戶端的依賴的創建和客戶端行為分離開來,這樣使得程式設計更加地松藕合,更符合依賴倒置及單一職責原則。它與客戶端瞭解依賴關係的服務定位器模式形成對比。”

如果不使用依賴註入技術,很難管理依賴並開發出一個模塊和結構良好的應用。

 

傳統方式的問題

在一個應用里,類之間相互依賴。假設我們有一個應用服務,它使用倉儲插入實體到資料庫,在這種情況下,應用服務類依賴於倉儲類,如下:

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", 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的所有能力來執行任何任務(像自定義註冊,註入鉤子,攔截器等等)。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天遇到了一個問題,要查詢今天添加到一個表中的數據,後來解決了,嗯,記錄一下: SELECT * FROM 表1 WHERE convert(varchar(10),表1.創建時間,120)=CONVERT(VARCHAR(10),GETDATE(),120) 如果是本月的話: SELECT * F ...
  • Centos6.5之ssh免密碼登錄配置 centos ssh 免密碼登錄 0.說明 這裡為了方便說明問題,假設有A和B兩台安裝了centos6.5的主機。目標是實現A、B兩台主機分別能夠通過ssh免密碼登錄到對方主機。不同主機的配置過程一樣,這裡介紹A主機的配置過程。 事先在AB主機分別創建好要免 ...
  • 一、說明 安裝環境 centos6.6 (64位) python2.7.10 (升級系統預設python版本的方法參見 "在CentOS 6.5上安裝python2.7" ) 約定 工作目錄假定為當前用戶的HOME目錄 安裝的各軟體版本以文檔編寫時的最新版為基準,請結合實際情況參考。 尖括弧""包括 ...
  • 簡介 ABP提供了一個緩存介面,它內部使用了這個緩存介面。雖然介面的預設實現是MemoryCache,但可以用任何其它實現的緩存供應器。Abp.RedisCache包用Redis實現了緩存(查看下方的“Redis 緩存集成”)。 ICacheManager 緩存的主要介面是ICacheManager ...
  • MySqlSugar 3.X API 作為支持.NET CORE 為數不多的ORM之一,除了具有優越的性能外,還擁有強大的功能,不只是滿足你的增,刪,查和改。實質上擁有更多你想像不到的功能,當你需要實現某個功能時會發現有這個功能太棒了。 所有版本 ASP.NET 4.0+ MSSQL https:/ ...
  • 作為物聯網通訊框架,肯定要支持多種通訊鏈路,在多種通訊鏈路的基礎上完成多種通訊協議的交互,例如:Modbus、自定義協議等等。但是,有一個問題:針對同一臺硬體設備或感測器,完成串口和網路兩種通訊方式的數據採集和控制,是否要分別寫代碼?如果從現實角度分析,同一硬體,它要完成的業務邏輯肯定是相同的,所以... ...
  • 代碼是敲出來的嗎?是批量生成出來的嗎? No no no,代碼是設計出來的! 如果說到代碼生成器,大家可能會想到三層、動軟代碼生成器、資料庫表等等。其一般的思路是,先有資料庫然後根據庫里的表自動生成一系列的代碼,包括實體類、持久化、業務層(空函數)、頁面代碼等,還可以生成資料庫文檔。這個確實很好很強 ...
  • 文檔目錄 本節內容: 簡介 關於 IAbpSeesion 註入會話 會話屬性 用戶標識符 關於 IAbpSeesion 簡介 如果一個應用需要登錄,它就需要知道當前用戶在執行的操作。儘管Asp.net自身在展現層提供了Session(會話)對象,而ABP提供IAbpSession介面來獲取當前用戶和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...