1. 程式人生 > >實戰Asp.Net Core:DI生命周期

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

readonly www 還需要 cal ise enc 返回值 route git

原文:實戰Asp.Net Core:DI生命周期


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

1、前言

Asp.Net Core 默認支持 DI(依賴註入) 軟件設計模式,那使用 DI 的過程中,我們勢必會接觸到對象的生命周期,那麽幾種不同的對象生命周期到底是怎麽樣的呢?我們拿代碼說話。

關於 DI 與 IOC:

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

2、DI生命周期

DI的生命周期,根據框架、庫的不同,會略有差異。此處,我們就以微軟的DI擴展為例,來說下DI中常用的幾種生命周期。

首先,我們想象一個這樣一個場景。假設我們有寄快遞的需求,那麽我們會致電快遞公司:“我們要寄快遞,派一個快遞員過來收貨”。接著,快遞公司會如何做呢?

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

這對應到生命周期就是:

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

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

2.1、驗證準備

首先,我們需要三個Services(Service1\Service2\Service3)內容一致,如下:

// Service1.cs,Service2、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.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.3、驗證作用域實例(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.4、驗證瞬時實例(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,都是全新的實例。

3、擴展(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

4、總結

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

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

本文github地址

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