ABP vNext系列文章03—依賴注入

一、依賴注入的類型註冊

ABP的依賴注入系統是基於Microsoft的依賴注入擴展庫(Microsoft.Extensions.DependencyInjection nuget包)開發的.因此,它的文檔在ABP中也是有效的.

也就是說我們在ABP中要想向IOC容器中注入類有兩種方式:

一是可以使用.netcore自帶的注入方法

public class MyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //在此處注入依賴項
context.Services.AddTransient<IMyCurrentUser, MyCurrentUser>();
    }
}

二是依照ABP約定的規則註冊

1、依賴介面

如果實現這些介面,則會自動將類註冊到依賴注入:

  • ITransientDependency 註冊為transient生命周期.
  • ISingletonDependency 註冊為singleton生命周期.
  • IScopedDependency 註冊為scoped生命周期.
    public class MyClass: ITransientDependency
    {
    }

    MyClass因為實現了ITransientDependency,所以它會自動註冊為transient生命周期.同理,其它的也是一樣

2、Dependency 特性

我們也可以給某個類打上特性標籤的方法來確定要注入的類

配置依賴注入服務的另一種方法是使用DependencyAttribute.它具有以下屬性:

  • Lifetime: 註冊的生命周期:Singleton,Transient或Scoped.
  • TryRegister: 設置true則只註冊以前未註冊的服務.使用IServiceCollection的TryAdd … 擴展方法.
  • ReplaceServices: 設置true則替換之前已經註冊過的服務.使用IServiceCollection的Replace擴展方法.

示例:

[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class MyClass
{

}

如果定義了Lifetime屬性,則Dependency特性具有比其他依賴介面更高的優先順序.

3、ExposeServices 特性

ExposeServicesAttribute用於控制相關類提供了什麼服務.例:
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{

}

TaxCalculator類只公開ITaxCalculator介面.這意味著你只能注入ITaxCalculator,但不能注入TaxCalculatorICalculator到你的應用程式中.

依照約定公開的服務

如果你未指定要公開的服務,則ABP依照約定公開服務.以上面定義的TaxCalculator為例:

 

  • 默認情況下,類本身是公開的.這意味著你可以按TaxCalculator類注入它.
  • 默認情況下,默認介面是公開的.默認介面是由命名約定確定.在這個例子中,ICalculatorITaxCalculatorTaxCalculator的默認介面,但ICanCalculate不是.

只要有意義,特性和介面是可以組合在一起使用的.

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator : ITaxCalculator, ITransientDependency
{

}

4、固定的註冊類型

一些特定類型會默認註冊到依賴注入.例子:

  • 模組類註冊為singleton.
  • MVC控制器(繼承ControllerAbpController)被註冊為transient.
  • MVC頁面模型(繼承PageModelAbpPageModel)被註冊為transient.
  • MVC視圖組件(繼承ViewComponentAbpViewComponent)被註冊為transient.
  • 應用程式服務(實現IApplicationService介面或繼承ApplicationService類)註冊為transient.
  • 存儲庫(實現IRepository介面)註冊為transient.
  • 域服務(實現IDomainService介面)註冊為transient.

示例:

public class BlogPostAppService : ApplicationService
{
}

BlogPostAppService 由於它是從已知的基類派生的,因此會自動註冊為transient生命周期.

如何選擇?

如果使用的是ABP框架,使用自帶的規則注入IOC是比較方便的,以下情況可以考慮手動註冊

在某些情況下,你可能需要向IServiceCollection手動註冊服務,尤其是在需要使用自定義工廠方法或singleton實例時.在這種情況下,你可以像Microsoft文檔描述的那樣直接添加服務.例:

public class BlogModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //註冊一個singleton實例
        context.Services.AddSingleton<TaxCalculator>(new TaxCalculator(taxRatio: 0.18));

        //註冊一個從IServiceProvider解析得來的工廠方法
        context.Services.AddScoped<ITaxCalculator>(sp => sp.GetRequiredService<TaxCalculator>());
    }
}

 

二、依賴注入的使用

使用已注入的服務有三種方法

1、構造函數注入

這是將服務注入類的最常用方法.例如:

public class TaxAppService : ApplicationService
{
    private readonly ITaxCalculator _taxCalculator;

    public TaxAppService(ITaxCalculator taxCalculator)
    {
        _taxCalculator = taxCalculator;
    }

    public void DoSomething()
    {
        //...使用 _taxCalculator...
    }
}

TaxAppService在構造方法中得到ITaxCalculator.依賴注入系統在運行時自動提供所請求的服務.

構造方法注入是將依賴項注入類的首選方式.這樣,除非提供了所有構造方法注入的依賴項,否則無法構造類.因此,該類明確的聲明了它必需的服務.

2、屬性注入

Microsoft依賴注入庫不支援屬性注入.但是,ABP可以與第三方DI提供商(例如Autofac)集成,以實現屬性注入.例:

public class MyService : ITransientDependency
{
    public ILogger<MyService> Logger { get; set; }

    public MyService()
    {
        Logger = NullLogger<MyService>.Instance;
    }

    public void DoSomething()
    {
        //...使用 Logger 寫日誌...
    }
}

對於屬性注入依賴項,使用公開的setter聲明公共屬性.這允許DI框架在創建類之後設置它.

屬性注入依賴項通常被視為可選依賴項.這意味著沒有它們,服務也可以正常工作.Logger就是這樣的依賴項,MyService可以繼續工作而無需日誌記錄.

為了使依賴項成為可選的,我們通常會為依賴項設置默認/後備(fallback)值.在此示例中,NullLogger用作後備.因此,如果DI框架或你在創建MyService後未設置Logger屬性,則MyService依然可以工作但不寫日誌.

屬性注入的一個限制是你不能在構造函數中使用依賴項,因為它是在對象構造之後設置的.

當你想要設計一個默認注入了一些公共服務的基類時,屬性注入也很有用.如果你打算使用構造方法注入,那麼所有派生類也應該將依賴的服務注入到它們自己的構造方法中,這使得開發更加困難.但是,對於非可選服務使用屬性注入要非常小心,因為它使得類的要求難以清楚地看到.

3、從IServiceProvider解析服務

 你可能希望直接從IServiceProvider解析服務.在這種情況下,你可以將IServiceProvider注入到你的類並使用GetService方法,如下所示:

public class MyService : ITransientDependency
{
    private readonly IServiceProvider _serviceProvider;

    public MyService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void DoSomething()
    {
        var taxCalculator = _serviceProvider.GetService<ITaxCalculator>();
        //...
    }
}

 

 重點:給服務註冊回調方法

IServiceCollection.OnRegistred 事件

你可能想在註冊到依賴注入的每個服務上執行一個操作, 在你的模組的 PreConfigureServices 方法中, 使用 OnRegistred 方法註冊一個回調(callback) , 如下所示:

public class AppModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.OnRegistred(ctx =>
        {
            var type = ctx.ImplementationType;
            //...
        });
    }
}

ImplementationType 提供了服務類型. 該回調(callback)通常用於向服務添加攔截器. 例如:

public class AppModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.OnRegistred(ctx =>
        {
            if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true))
            {
                ctx.Interceptors.TryAdd<MyLogInterceptor>();
            }
        });
    }
}

這個示例判斷一個服務類是否具有 MyLogAttribute 特性, 如果有的話就添加一個 MyLogInterceptor 到攔截器集合中.

注意, 如果服務類公開了多於一個服務或介面, OnRegistred 回調(callback)可能被同一服務類多次調用. 因此, 較安全的方法是使用 Interceptors.TryAdd 方法而不是 Interceptors.Add 方法.

三、依賴注入源碼解析

 在介面註冊時你可以看到

ABP vNext 仍然在其 Core 庫為我們提供了三種介面,即 ISingletonDependency 和 ITransientDependency 、IScopedDependency 介面,方便我們的類型/組件自動註冊,這三種介面分別對應了對象的 單例瞬時範圍 生命周期。只要任何類型/介面實現了以上任意介面,ABP vNext 就會在系統啟動時候,將這些對象註冊到 IoC 容器當中。

那麼究竟是在什麼時候呢?回顧上一章的文章ABP vNext系列文章01—模組化 – zhengwei_cq – 部落格園 (cnblogs.com),在模組系統調用模組的 ConfigureService() 的時候,就會有一個 services.AddAssembly(module.Type.Assembly) ,他會將模組的所屬的程式集傳入。

那我們就從AbpApplicationBase類這個ConfigureService()方法開始分析源碼吧

 

 

 繼續進入ConfigureService()方法,我們知道在這個方法中主要是拿出之前載入的所有的模組,分別調用模組中的各種生命周期的方法

其中有一如下一段非常重要的程式碼

 

 

 

SkipAutoServiceRegistration是默認的值為false,核心的程式碼為 Services.AddAssembly(assembly);

進入這個擴展方法

 

 

 發現里有一個比較核心的擴展方法GetConventionalRegistrars(),,主要是/獲得所有規約註冊器,然後調用規約註冊器的 AddAssmbly 方法註冊類型。

那這個規約器到底是什麼呢,繼續進入

 

 

 在這個方法中可以看到,如果沒有獲取到規約器就是默認的DefaultConventionalRegistrar對象

那就從這個類開始研究了,進入此類,發現他的基類是ConventionalRegistrarBase,基類又實現了介面IConventionalRegistrar,看看此介面的方標準:

 

 

 該介面定義了三個方法,支援傳入程式集、類型數組、具體類型

再將回到抽像基類ConventionalRegistrarBase,最終調用的方法實現就是該類的擴展方法AddAssembly

 

 

 先是獲取了程式集中的所有類,去掉了抽像類和泛型類,再調用AddTypes方法 來將類型註冊到 IServiceCollection 當中的。

查看這個方法,發現這個設計比較巧了,抽象類中這個方法也是抽象的,此方法被延遲到了子類中去實現了,我們就只能回到默認的規約類中看看:

 

 

 該方法中主要是根據類的注入規則注入到服務中的,除了對三種生命周期介面處理之外,如果類型使用了 DependencyAttribute 特性,也會根據該特性的參數配置進行不同的註冊邏輯。

但是從這個方法中我們只知道是否確定要不要把這個類注入到容器中,到底是怎麼確定他注入的的生命周期的呢?

此時我們注意到方法GetLifeTimeOrNull(),在此方法前看到還獲取了此類的特性GetDependencyAttributeOrNull(type),可能,發特性標籤要優先於介面處理的

進入GetLifeTimeOrNull(type, dependencyAttribute);方法

 

 這裡補充一個小知道點 IsAssignableFrom方法,主要是用於判斷兩個類是否兼容,也就是說後者如果前者的實現類或者子類或者相同的類,則返回true.

那麼到為此,我們就知道了,為什麼我們在定義自己的類如果實現了指定的介面 或者將類上打上指定的特性後會將自己定義的類注入到容器了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Tags: