入門系列-依賴注入

依賴注入

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

模塊化

由於ABP是一個模塊化框架,因此每個模塊都定義它自己的服務並在它自己的單獨模塊類中通過依賴注入進行註冊.例:

public class BlogModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //在此處注入依賴項
    }
}

依照約定的註冊

ABP引入了依照約定的服務註冊.依照約定你無需做任何事,它會自動完成.如果要禁用它,你可以通過重寫PreConfigureServices方法,設置SkipAutoServiceRegistrationtrue.

public class BlogModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        SkipAutoServiceRegistration = true;
    }
}

一旦跳過自動註冊,你應該手動註冊你的服務.在這種情況下,AddAssemblyOf擴展方法可以幫助你依照約定註冊所有服務.例:

public class BlogModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        SkipAutoServiceRegistration = true;
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAssemblyOf<BlogModule>();
    }
}

以下部分解釋了約定和配置.

固有的註冊類型

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

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

示例:

public class BlogPostAppService : ApplicationService
{
}

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

依賴接口

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

  • ITransientDependency 註冊爲transient生命週期.
  • ISingletonDependency 註冊爲singleton生命週期.
  • IScopedDependency 註冊爲scoped生命週期.

示例:

public class TaxCalculator : ITransientDependency
{
}

TaxCalculator因爲實現了ITransientDependency,所以它會自動註冊爲transient生命週期.

Dependency 特性

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

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

示例:

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

}

TaxCalculator類只公開ITaxCalculator接口.這意味着你只能注入ITaxCalculator,但不能注入TaxCalculatorICalculator到你的應用程序中.

如果定義了Lifetime屬性,則Dependency特性具有比其他依賴接口更高的優先級.

ExposeServices 特性

ExposeServicesAttribute用於控制相關類提供了什麼服務.例:

[ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{

}

依照約定公開的服務

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

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

組合到一起

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

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

}

手動註冊

在某些情況下,你可能需要向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>());
    }
}

注入依賴關係

使用已註冊的服務有三種常用方法.

構造方法注入

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

public class TaxAppService : ApplicationService
{
    private readonly ITaxCalculator _taxCalculator;

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

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

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

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

屬性注入

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依然可以工作但不寫日誌.

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

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

從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>();
        //...
    }
}

釋放/處理(Releasing/Disposing)服務

如果你使用了構造函數或屬性注入,則無需擔心釋放服務的資源.但是,如果你從IServiceProvider解析了服務,在某些情況下,你可能需要注意釋放服務.

ASP.NET Core會在當前HTTP請求結束時釋放所有服務,即使你直接從IServiceProvider解析了服務(假設你注入了IServiceProvider).但是,在某些情況下,你可能希望釋放/處理手動解析的服務:

  • 你的代碼在AspNet Core請求之外執行,執行者沒有處理服務範圍.
  • 你只有對根服務提供者的引用.
  • 你可能希望立即釋放和處理服務(例如,你可能會創建太多具有大量內存佔用且不想過度使用內存的服務).

在任何情況下,你都可以使用這樣的using代碼塊來安全地立即釋放服務:

using (var scope = _serviceProvider.CreateScope())
{
    var service1 = scope.ServiceProvider.GetService<IMyService1>();
    var service2 = scope.ServiceProvider.GetService<IMyService2>();
}

兩個服務在創建的scope被處理時(在using塊的末尾)釋放.

高級特性

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 方法. 請參閱動態代理(dynamic proxying)/攔截器 文檔.

第三方提供程序

雖然ABP框架沒有對任何第三方DI提供程序的核心依賴, 但它必須使用一個提供程序來支持動態代理(dynamic proxying)和一些高級特性以便ABP特性能正常工作.

啓動模板中已安裝了Autofac. 更多信息請參閱 Autofac 集成 文檔.

請參閱

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章