ASP.NET Core 注册单例方案
一個單例是沒有公共構造函數的,只能通過靜態的 Instance?屬性獲取,這是單例的標準初衷,一個單例是不想讓別人調用它的構造函數的。但是 aspnetcore?中提供的?AddSingleton<TService, TImplementation>() ,只提供了類型,而無法注入對象實例,單實例對象還是要框架深層構造的,這實際上并不是安全的做法。?
如果使用了標準的單例設計方法,則無法由框架直接生成單實例,這就需要使用點小技巧了。?
單例設計
public class Singleton<T> where T : class {protected static T instance;protected static object locker = new object();public static T Instance{get{if (instance == null){lock (locker){if (instance == null) //防止線程重入{IEnumerable<ConstructorInfo> constructors = typeof(T).GetTypeInfo().DeclaredConstructors;ConstructorInfo ci = constructors.ToList()[0];instance = ci.Invoke(new object[] { }) as T;}}}return instance;}}protected Singleton(){IEnumerable<ConstructorInfo> constructors = typeof(T).GetTypeInfo().DeclaredConstructors;constructors.ActionForeach(c => {if (c.IsPublic){throw new InvalidOperationException("禁止從public構造函數中實例化!");}});}}以上要點有三個:
1?使用次 if?判斷和 locker?保護,防止線程重入時構造多個實例,確保唯一性;
2?在基類中,使用反射調用子類的構造函數完成實例化;
3 基類的構造函數是受保護的,它會檢查,禁止子類的公共構造函數調用。
第3條隱藏了一個知識點:子類在初始化實例時,默認會調用基類的構造函數。
因為以上三條機制確保了單例的唯一性,所以反射只會在第一次使用時調用,對性能的影響可以忽略不計。
?
單例的使用
使用起來非常簡單
public class Root:Singleton<Root> {protected Root() { }public void DoSomething() {Console.WriteLine("I feel very happy, cus I'm unique."); } }假設有一個類,叫做Root,由于業務需要,它必須要以單例實現,顧名思義,根只能有一個。
如果不重寫 protected?構造函數,則可能發生以下情況:
Root root = new Root();這是被禁止的,萬一忘記寫了 protected Root(){}?這一行,就會拋出異常,可見在??Singleton<T>?中進行判斷,是十分有必要的。
在業務需要的地方,就可以用通常使用的單例模式來調用 :
Root.Instance.DoSomething();至此,單例模式完成。
一個真實的業務場景
假設主模塊是?Main.dll,?它是一個 aspnetcore?工程, 它調用了?A.dll?作為它的類庫。由于某種原因,Root?類必須要在?Main?工程中實現,而不能放到?A?工程中。但是A工程要用到?Root?的方法。如果讓?A?工程來引用? Main?工程,這就是反向引用了,這會形成循環引用,是不被允許的。
所以我們可以把 Root?的方法抽象出接口來,注冊到?aspnet?框架中,我們可以這樣做:
在?A.csproj?中,暴露接口給自己調用:
public interface IRoot {void DoSomething(); }在 Main.csproj?中實現這個接口:
class Root:Singleton<Root>, IRoot {protected Root() { }public void DoSomething() {Console.WriteLine("I feel very happy, cus I'm unique.");} }然后就是注冊了,在 Main.csproj?工程的?Startup.cs?文件的??ConfigureServices?方法中進行注冊:
public void ConfigureServices(IServiceCollection services) {services.AddControllersWithViews();services.AddSession();//...其他代碼services.AddSingleton<IRoot, Root>(); }這樣就可以了嗎? no no no ,?這樣肯定是不行的,因為前面我們設計的單例,是這樣使用的: Root.Instance.DoSomething();? 而不是 Root root = new Root();?
這會導致注入失敗,因為框架注冊要求 Root 有一個公共無參構造函數,況且它并不知道Root有個靜態屬性 Instance ,而且只能通過 Instance?來訪問。
單例注入方案
下面說到正題了,既然不能直接注冊單例,我們可以使用一個中間接口來注入,這個中間接口提供了單例的訪問對象,而且它擁有一個沒有寫出來的默認公共構造方法,它的構造函數,與 Root 類的構造函數,毫無關系,所以可以由框架創建。
在 A.csproj?工程中定義:
public interface IRootProvider {IRoot Root { get; } }在?Main.csproj?中實現:
public class RootProvider : IRootProvider {public IRoot Root { get => SomeNamespace.Root.Instance; } }然后再注冊:
public void ConfigureServices(IServiceCollection services) {services.AddControllersWithViews();services.AddSession();//...其他代碼services.AddScoped<IRootProvider, RootProvider>();services.AddScoped<ISomeService, SomeService>(); }look,?我們已經不需要使用?AddSingleton?來注入了,因為? RootProvider?不必是單實例的。?
在?A.csproj? 中愉快地使用:
public class SomeService:ISomeService {private IRoot root;public SomeService(IRootProvider rootProvider){this.root = rootProvider.Root;}public void SomeBusiness(){this.root.DoSomething();} } SomeService 是在主模塊中注入的服務,在主模塊中構造,構造的前提是要有一個 IRootProvider 的實例,同時 IRootProvider 要在它的前一行注冊,這個很重要。 到這里,本文就結束了,但我還是想啰嗦一下:在A中使用的 IRoot root 一點也看不出單例的痕跡,因為 IRoot 只是一個業務接口;同時 IRootProvider 也只提供了 IRoot 的 get 方法, 所以對于 A 模塊的開發者,完全不必知道 Root 的存在,更不必知道什么 Singleton<Root> 跟 Instance 的破事。我們已經完全隱藏了單例模式的實現,這是解決這個問題附帶的收獲。這個設計是不是徹底實現了 面向接口編程 的規范? 快夸我吧! 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的ASP.NET Core 注册单例方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.NET Core Blazor
- 下一篇: 将 .NET Framework 项目转