實戰Asp.Net Core:DI生命週期


title: 實戰Asp.Net Core:DI生命週期
date: 2018-11-30 21:54:52
---html

一、前言

Asp.Net Core 默認支持 DI(依賴注入) 軟件設計模式,那使用 DI 的過程當中,咱們勢必會接觸到對象的生命週期,那麼幾種不一樣的對象生命週期究竟是怎麼樣的呢?咱們拿代碼說話。git

關於 DI 與 IOC:github

我的理解:IOC(控制反轉) 是目的(下降代碼、服務間的耦合),而 DI 是達到該目的的一種手段(具體辦法)。c#

二、DI生命週期

DI的生命週期,根據框架、庫的不一樣,會略有差別。此處,咱們就以微軟的DI擴展爲例,來講下DI中經常使用的幾種生命週期。設計模式

首先,咱們想象一個這樣一個場景。假設咱們有寄快遞的需求,那麼咱們會致電快遞公司:「咱們要寄快遞,派一個快遞員過來收貨」。接着,快遞公司會如何作呢?多線程

  1. 一直派遣同一個快遞員來收貨。
  2. 第一週派遣快遞員A、第二週派遣快遞員B收貨。
  3. 每次都派遣一個新的快遞員收貨。

這對應到生命週期就是:框架

  1. 單例(Singleton),單一實例,每次使用都是該實例。
  2. 做用域實例(Scoped),在一個做用域(好比單次請求)內是同一個實例,不一樣的做用域實例不一樣。
  3. 瞬時實例(Transient),每次使用都建立新的實例。

快遞公司也就是咱們在DI中常說的容器(Container)了。性能

2.一、驗證準備

首先,咱們須要三個Services(Service1\Service2\Service3)內容一致,以下:this

// Service1.cs,Service二、Service3除類名之外,內容一致
public class Service1
{
    private int value = 0;

    public int GetValue()
    {
        this.value++;
        return this.value;
    }
}

而後,咱們須要一個業務類,再一次注入這三個Service,內容以下:線程

// DefaultBusiness.cs 
public class DefaultBusiness
{
    private readonly Service1 s1;
    private readonly Service2 s2;
    private readonly Service3 s3;

    public DefaultBusiness(Service1 s1, Service2 s2, Service3 s3)
    {
        this.s1 = s1;
        this.s2 = s2;
        this.s3 = s3;
    }

    public int GetS1Value()
    {
        return this.s1.GetValue();
    }

    public int GetS2Value()
    {
        return this.s2.GetValue();
    }

    public int GetS3Value()
    {
        return this.s3.GetValue();
    }
}

最後,還須要在Startup.cs進行注入

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
  // 單例模式
  services.AddSingleton<Service1>();
  // 做用域模式
  services.AddScoped<Service2>();
  // 瞬時模式
  services.AddTransient<Service3>();
  // 爲了保證結果,將Business類註冊爲瞬時模式,每次注入都是全新的。
  services.AddTransient<Business.DefaultBusiness>();
}

2.二、驗證單例(Singleton)

對於單例來講,咱們指望,在整個程序啓動期間,都是同一個實例,因此,咱們只須要在Service中,增長一個局部變量,作累加就能夠驗證。

// DefaultController.cs
[Route("singleton")]
public IActionResult SingletonTest()
{
    this.defaultBiz.GetS1Value();
    return Json(new
    {
        s1 = s1.GetValue()
    });
}

而後咱們訪問 http://localhost:5000/singleton 屢次,輸入以下:

// 第一次
{ s1: 2 }
// 第二次
{ s1: 4 }
// 第三次
{ s1: 6 }

能夠發現,每請求一次,value都會增長2。分析下怎麼來的呢?

  1. 首先是執行 defaultBiz.GetValue() 中,根據內部代碼,此處會對注入的實例,value + 1。
  2. 以後,Json()中的,s1 = s1.GetValue(),此處再一次增長了1。
  3. 綜上,一次請求,s1 中的value值會增長2,因爲是單例模式,在整個服務運行期間,都是一個實例,因此,每次請求都會累加2。

2.三、驗證做用域實例(Scoped)

// DefaultController.cs
[Route("scoped")]
public IActionResult ScopedTest()
{
    this.defaultBiz.GetS2Value();
    return Json(new
    {
        s2 = s2.GetValue()
    });
}

而後咱們訪問 http://localhost:5000/scoped 屢次,輸入以下:

// 第一次
{ s2: 2 }
// 第二次
{ s2: 2 }
// 第三次
{ s2: 2 }

從結果能夠看出,每次請求的返回值是固定的,都爲2,也就是證實了Service2中,value++執行了兩次。對於執行value++的代碼,只有 defaultBiz.GetS2Value()s2 = s2.GetValue(),因此這兩處操做的是同一個實例。這也就證實了,對於 Scoped 生命週期,在做用域(能夠簡單理解爲單次請求,實際上並不許確,注意,此處爲考慮多線程的狀況)內,都是使用的同一個實例。在不一樣的請求之間,則是不一樣的實例。

2.四、驗證瞬時實例(Transient)

// DefaultController.cs
[Route("transient")]
public IActionResult TransientTest()
{
    return Json(new
    {
        s3 = s3.GetValue()
    });
}

而後咱們訪問 http://localhost:5000/transient 屢次,輸入以下:

// 第一次
{ s3: 1 }
// 第二次
{ s3: 1 }
// 第三次
{ s3: 1 }

從結果來看,每次請求的都是相同的返回值,s3 = 1,這說明了,兩次操做的value++,是針對的不一樣實例。也就是每次使用 Service1,都是全新的實例。

三、擴展(Autofac DI 類庫)

Asp.Net Core中默認的DI,相對仍是比較簡單的,只有三個生命週期。對於時下比較的依賴注入庫,通常都會有更多的生命週期,有些還會有生命週期事件能夠監控。

Autofac 爲例,該類庫提供了以下一些生命週期,能夠作到更精細化的控制:

  1. 單次依賴(Instance Per Dependency)- 也就是Transient,每次獲取實例都是全新的。
  2. 單例(Single Instance) - 也就是單例,整個服務週期都是一個實例。
  3. 做用域隔離的實例(Instance Per Lifetime Scope) - 也就是一個做用域一個,示例代碼以下:
// 先建立做用域
using(var scope1 = container.BeginLifetimeScope())
{
  
  for(var i = 0; i < 100; i++)
  {
    // 在做用域內,Resolve 的都是同一個實例
    var w1 = scope1.Resolve<Worker>();
  }
}

// 建立另外一個做用域
using(var scope2 = container.BeginLifetimeScope())
{
  for(var i = 0; i < 100; i++)
  {
    // 在做用域內,Resolve 的都是同一個實例,可是這個實例和 scope1 做用域中的 w1 不是同一個。
    var w2 = scope2.Resolve<Worker>();
  }
}
  1. 帶標籤的做用域隔離實例(Instance Per Matching Lifetime Scope)
  2. 單次請求做用域實例(Instance Per Request) - 每一個請求做爲一個做用域。
  3. 指定Owner的做用域實例(Instance Per Owned)- 對於同一個Owner,實例保持一致
  4. 線程做用域實例(Thread Scope)

更多 Autofac 生命週期相關內容,請參考:https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html

四、總結

本文主要簡單演示了 Asp.Net Core 中默認的幾種服務生命週期效果,也拋磚引玉的說了下 Autofac 中的服務生命週期。合理的利用生命週期,能夠減小對象的建立,提交程序性能。可是,用錯了生命週期,則容易產生隱含的bug。在使用 DI 類庫的時候,必定要理解清楚不一樣的生命週期的應用場景。

本文示例代碼:https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo

本文github地址

相關文章
相關標籤/搜索