重新整理 .net core 實踐篇————依賴注入應用[二]

不問前世發表於2021-05-25

前言

這裡介紹一下.net core的依賴注入框架,其中其程式碼原理在我的另一個整理<<重新整理 1400篇>>中已經寫了,故而專門整理應用這一塊。

以下只是個人整理,如有問題,望請指點。

正文

為什麼有這個依賴注入呢?

假設人們面臨這樣一個問題。

比如說一個人做飛機去北京。那麼人和飛機有什麼關係呢?人和北京有什麼關係呢?

假設有3個主體類,一個是人一個是飛機一個是北京。他們之間到底應該怎麼組織呢?

先說人和北京的關係,人要去北京,那麼飛機就是一個交通工具。是的,只是一個交通工具,那麼人和飛機就沒有直接的關係,而是人通過交通工具去了北京。

同樣北京也可以抽象處理,北京是一個地點。那麼這個語句就變成了"人通過交通工具去了某個地方",這個交通工具可以是飛機可以是高鐵,未來可以是火箭。地點同樣如此。

如果抓住這個詞,“未來”,那麼其擴充套件性就很強了,不僅現在擁有的還代表以後。就像我們的usb介面一樣,未來可能有新的裝置只要適配了usb介面,那麼我主機裡面的東西將不用動任何東西就可以執行新的裝置。

因為我的主機沒有動任何東西,那麼是不是代表我的穩定性?所以依賴注入不僅可以確保我們程式碼的可維護性和可擴充套件性,最重要的是穩定性(當然了穩定性是可維護性的一種),而程式碼最重要的就是穩定性。

在asp.net core 的整個架構中,依賴注入框架提供了物件建立和生命週期管理的核心能力,各個元件相互協作,可以說是整個框架的核心了。

那麼這裡有一個關鍵詞生命週期,將會伴隨著整篇文章的核心。

.net core 的依賴實現主要是通過 Microsoft.Extensions.DependencyInjection;

其這個包,是實現了Microsoft.Extensions.DependencyInjection.Abstrations;

Microsoft.Extensions.DependencyInjection.Abstrations是介面的定義,Microsoft.Extensions.DependencyInjection 是介面的實現。

那麼這裡就可以想象到既然提供了介面,那麼一般來說就有第三方實現,後面會介紹第三方實現。

依賴注入的核心由下面幾個部門組成。

  1. IServiceCollection 負責服務的註冊

  2. ServiceDescriptor 每一個服務註冊時候的資訊

  3. IServiceProvider 具體的容器

  4. IServiceScope 容器的子容器的生命週期

那麼什麼是生命週期呢?

  1. 單例模式

  2. 作用域模式

  3. 瞬時模式

那麼這個怎麼理解呢?首先建立起這樣一個模型。

這個模型是什麼意思呢? 首先我們建立單例模式產生的物件會存在root中,如果作用域模式那麼建立的物件將會在某個作用域中,這個作用域是自己指定的。

而瞬時模式呢,就是不儲存在任何一個作用域中。

當我們通過這個依賴注入服務建立物件的時候,如果是單例模式,那麼會這麼幹,查詢root容器中有沒有該註冊資訊的例項,如果有就直接返回,如果沒有那麼建立儲存在root容器中。

如果是作用域模式,那麼會去找當前作用域有沒有該註冊資訊的例項,如果有那麼直接返回,如果沒有那麼儲存在當前作用域的容器中。

同樣,如果作用域模式,那麼該作用域的容器消失了,裡面儲存的東西就沒了,那麼自然通過該作用域建立的物件就消失了。

那麼就用程式碼來實驗一下。

有三個類:

裡面的內容就是一個介面,然後一個具體的類,這裡演示其中一個。

public interface IMySingletonService
{
}
public class MySingletonService: IMySingletonService
{
}

然後再configureServices 中進行註冊:

services.AddSingleton<IMySingletonService, MySingletonService>();
services.AddScoped<IMyScopedService, MyScopedService>();
services.AddTransient<IMyTransientService, MyTransientService>();

然後寫一個http的get方法。

[HttpGet]
public int GetService([FromServices] IMySingletonService mySingleton1,
	[FromServices] IMySingletonService mySingleton2,
	[FromServices]IMyScopedService myScoped1,
	[FromServices] IMyScopedService myScoped2,
	[FromServices] IMyTransientService myTransient1,
	[FromServices] IMyTransientService myTransient2)
{
	Console.WriteLine($"singleton1:{mySingleton1.GetHashCode()}");
	Console.WriteLine($"singleton1:{mySingleton2.GetHashCode()}");
	Console.WriteLine($"myScoped1:{myScoped1.GetHashCode()}");
	Console.WriteLine($"myScoped2:{myScoped2.GetHashCode()}");
	Console.WriteLine($"myTransient1:{myTransient1.GetHashCode()}");
	Console.WriteLine($"myTransient2:{myTransient2.GetHashCode()}");
	return 1;
}

這裡通過hashcode 來驗證是否兩個物件是否相等。

截圖如下:

然後我們再訪問一次這個介面。

上述可以得出,每一個http請求將會在同一個子容器中,且同一子容器獲取的註冊物件相同。而單例每次都相同。

transient 則是每次獲取到不同的物件。

好了,現在生命週期明瞭了,下面看一下注冊的方式。
我們通過這種方式進行註冊:

services.AddSingleton<IMySingletonService, MySingletonService>();

那麼是否還有其他方式?

還可以這樣:

services.AddSingleton<IMySingletonService>(new MySingletonService());
services.AddSingleton<IMySingletonService>(ServiceProvider =>
{
	return new MySingletonService();
});

為什麼需要這樣呢?道理也是很簡單的,因為我們可能需要一些自己定製的動態引數啊。
比如說:

services.AddSingleton<IMySingletonService>(ServiceProvider =>
{
	return new MySingletonService();
});

這種工廠模式,完全可以通過其他的註冊服務(ServiceProvider)的引數來例項化我們的MySingletonService。

還有值得注意的是,因為我們可以多次注入。那麼我們按道理可以獲取到註冊的全部。可以通過這種方式。

比如說:

services.AddSingleton<IMySingletonService, MySingletonService>();
services.AddSingleton<IMySingletonService>(new MySingletonService());
services.AddSingleton<IMySingletonService>(ServiceProvider =>
{
	return new MySingletonService();
});

通過AddSingleton呼叫了3次。

[HttpGet]
public int GetService([FromServices] IEnumerable<IMySingletonService> mySingleton1)
{
	foreach (var item in mySingleton1)
	{
		Console.WriteLine(item.GetHashCode());
	}

	return 1;
}

然後獲取一下。

發現可以一對多了,分別獲取到3個不同註冊的。

那麼如果我們的邏輯比較複雜,可能會多次呼叫到怎麼破?

services.AddSingleton<IMySingletonService, MySingletonService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMySingletonService, MySingletonService>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMySingletonService, MySingletonService>());

可以通過這種方式呼叫,如果註冊資訊相同就不註冊,如果不同就註冊。

這樣就只有一個。

如果你只想註冊一個的話可以這樣:

services.AddSingleton<IMySingletonService, MySingletonService>();
services.TryAddSingleton<IMySingletonService, MySingletonService>();
services.TryAddSingleton<IMySingletonService, MySingletonService>();

這個就是如果有註冊資訊,就不再註冊了。

還有其他的如替換註冊資訊,移除註冊資訊等,可以參考官網。

然後可以關注一下這個using Microsoft.Extensions.DependencyInjection.Extensions;,這裡面是一些擴充套件的,如有自己需要的一般都在這裡面了。

另一個值得注意的地方就是泛型模板的註冊。

public interface IGeneticService<T>
{
	public T getData();
}

public class GeneticService<T>: IGeneticService<T>
{
	public T Data { get; private set; }

	public GeneticService(T data)
	{
		this.Data = data;
	}

	public T getData()
	{
		return Data;
	}
}

那麼通過這種註冊:

services.AddSingleton(typeof(IGeneticService<>),typeof(GeneticService<>));

然後實現一個介面:

[HttpGet]
public int GetService([FromServices] IMySingletonService mySingletonService, [FromServices] IGeneticService<IMySingletonService> geneticService)
{
	Console.WriteLine(geneticService.getData().GetHashCode());
	Console.WriteLine(mySingletonService.GetHashCode());
	return 1;
}

這裡埋了一個點:

services.AddSingleton<IMySingletonService, MySingletonService>();
services.AddSingleton<IMySingletonService>(new MySingletonService());
services.AddSingleton<IMySingletonService>(ServiceProvider =>
{
	return new MySingletonService();
});

那就是上面這三種註冊方式的物件釋放行為是不一樣的,如果不瞭解的話,那麼可能你的服務跑著跑著記憶體就跑高了。下一節將會解釋到。

上述只是個人整理,如有錯誤,望請指出,謝謝,一天一更。上述只是應用,如程式碼原理,請檢視<<重新整理.net core 1400篇>>。

相關文章