入門系列-依賴注入

Johan.發表於2019-12-05

依賴注入

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 整合 文件.

請參閱

相關文章