前言 在 ASP.NET Core 中,微軟提供了一套預設的依賴註入實現,該實現對應的包為: ,我們可以通過查看其對應的開源倉庫看一下它的具體實現。基於該實現,我們不必顯式創建我們的服務對象,可以將其統一註入到 ServiceProvider 中進行集中維護,使用的時候直接在該對象中獲取即可。讓我們 ...
前言
在 ASP.NET Core 中,微軟提供了一套預設的依賴註入實現,該實現對應的包為:Microsoft.Extensions.DependencyInjection
,我們可以通過查看其對應的開源倉庫看一下它的具體實現。基於該實現,我們不必顯式創建我們的服務對象,可以將其統一註入到 ServiceProvider 中進行集中維護,使用的時候直接在該對象中獲取即可。讓我們在編寫業務邏輯時,不用太關註對象的創建和銷毀。這也是為什麼現在有些最佳實踐中建議不要過多使用 New 的方式來獲取對象。在本文中,我們將一起瞭解一下如何實現一個自己的 ServiceProvider。
自己動手,豐衣足食
為了方便區分,我這裡自定義定義的類叫:ServiceLocator,其功能與官方的 ServiceProvider 類似。
基本實現
首先,我們需要定義一個簡單的服務發現介面,用於約束上層具體的實現,示例代碼如下所示:
public interface IServiceLocator
{
void AddService<T>();
T GetService<T>();
}
接著,我們定義一個繼承上述介面的具體實現類,示例代碼如下所示:
public class ServiceLocator : IServiceLocator
{
private readonly IDictionary<object, object> services;
public ServiceLocator()
{
services = new Dictionary<object, object>();
}
public void AddService<T>()
{
services.TryAdd(typeof(T), Activator.CreateInstance<T>());
}
public T GetService<T>()
{
try
{
return (T)services[typeof(T)];
}
catch (KeyNotFoundException)
{
throw new ApplicationException("The requested service is not registered");
}
}
}
這樣,我們就實現了一個最基本的服務發現類,通過該類,我們可以將我們的多個服務進行集中管理。這裡我為了方便,模擬了 3 個服務類用於註冊,示例代碼如下所示:
public interface IService
{
void SayHello();
}
public class ServiceA : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from A");
}
}
public class ServiceB : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from B");
}
}
public class ServiceC : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from C");
}
}
使用方式就很簡單了,如下所示:
class Program
{
static void Main(string[] args)
{
IServiceLocator locator = new ServiceLocator();
locator.AddService<ServiceA>();
locator.AddService<ServiceB>();
locator.AddService<ServiceC>();
locator.GetService<ServiceA>().SayHello();
locator.GetService<ServiceB>().SayHello();
locator.GetService<ServiceC>().SayHello();
}
}
程式運行效果如下圖所示:
程式看起來運行不錯,結果也符合我們的預期。但是稍微有點工作經驗的朋友就會發現上述的實現是有很多潛在問題的。對於 IServiceLocator
的實例,我們一般會以單例模式來進行使用,這就會設計到線程安全的委托,所以我們的服務列表必須要是線程安全的。此外,如果我們需要註冊的服務過多,通過上述方式來進行註冊的話會加到系統開銷,因為我們的服務一旦註冊進去就會立刻被初始化,從而耗費不必要的系統記憶體,所以我們應該讓其實例化推遲,在使用的時候才進行實例化操作。下麵我們對上述問題一一進行改進。
單例模式
單例模式是一種最簡單也是使用最頻繁的設計模式,單例模式本身也有很多形式,感興趣的可以查看我之前的博文:設計模式系列 - 單例模式,這裡,我採用 線程安全 方式來修改我們的 ServiceLocator
,此外,我們還需要將我們的服務集合類修改為線程安全類型。所以,整個修改完畢後,示例代碼如下所示:
public class ServiceLocator : IServiceLocator
{
private static ServiceLocator _instance;
private static readonly object _locker = new object();
public static ServiceLocator GetInstance()
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = new ServiceLocator();
}
}
}
return _instance;
}
private readonly IDictionary<object, object> services;
private ServiceLocator()
{
services = new ConcurrentDictionary<object, object>();
}
public void AddService<T>()
{
services.TryAdd(typeof(T), Activator.CreateInstance<T>());
}
public T GetService<T>()
{
try
{
return (T)services[typeof(T)];
}
catch (KeyNotFoundException)
{
throw new ApplicationException("The requested service is not registered");
}
}
}
延遲載入
要想讓所有的註冊的服務支持懶載入,我們需要引入一個新的集合,這個新的集合是用於存儲我們相應的實例對象,在註冊的時候我們只記錄註冊類型,在需要訪問到相應的服務時,我們只需要在這個實例集合列表中訪問,如果發現我們需要的服務還未被實例化,那我們再進行實例化,然後將該實例化對象存儲起來並返回。對於用哪種數據結構來存,我們可以採用多種數據結構,我這裡仍然採用字典來存儲,示例代碼如下所示:
public class ServiceLocator : IServiceLocator
{
private static ServiceLocator _instance;
private static readonly object _locker = new object();
public static ServiceLocator GetInstance()
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = new ServiceLocator();
}
}
}
return _instance;
}
private readonly IDictionary<Type, Type> servicesType;
private readonly IDictionary<Type, object> instantiatedServices;
private ServiceLocator()
{
servicesType = new ConcurrentDictionary<Type, Type>();
instantiatedServices = new ConcurrentDictionary<Type, object>();
}
public void AddService<T>()
{
servicesType.Add(typeof(T), typeof(T));
}
public T GetService<T>()
{
if (!instantiatedServices.ContainsKey(typeof(T)))
{
try
{
ConstructorInfo constructor = servicesType[typeof(T)].GetConstructor(new Type[0]);
Debug.Assert(constructor != null, "Cannot find a suitable constructor for " + typeof(T));
T service = (T)constructor.Invoke(null);
instantiatedServices.Add(typeof(T), service);
}
catch (KeyNotFoundException)
{
throw new ApplicationException("The requested service is not registered");
}
}
return (T)instantiatedServices[typeof(T)];
}
}
自匹配構造
上面的所有改進都支持無參構造函數的服務,但是對於有參構造函數的服務註冊,我們定義的 服務提供者就不滿足的,因為上述的反射方式是不支持有參構造函數的。對於這種情況我們有兩種解決辦法。第一種是將服務的初始化放到最上層,然後 ServiceLocator 通過一個 Fun 的方式來獲取該示例,並存儲起來,我們稱之為 顯示創建
。第二種方式依然是通過反射方式,只是這個反射可能會複雜一下,我們稱之為 隱式創建
。我們分別對於這兩個實現方式進行代碼示例。
- 顯示構造
public interface IServiceLocator
{
void AddService<T>();
//新增介面
void AddService<T>(Func<T> Implementation);
T GetService<T>();
}
public class ServiceLocator : IServiceLocator
{
private static ServiceLocator _instance;
private static readonly object _locker = new object();
public static ServiceLocator GetInstance()
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = new ServiceLocator();
}
}
}
return _instance;
}
private readonly IDictionary<Type, Type> servicesType;
private readonly IDictionary<Type, object> instantiatedServices;
private ServiceLocator()
{
servicesType = new ConcurrentDictionary<Type, Type>();
instantiatedServices = new ConcurrentDictionary<Type, object>();
}
public void AddService<T>()
{
servicesType.Add(typeof(T), typeof(T));
}
//新增介面對應的具體實現
public void AddService<T>(Func<T> Implementation)
{
servicesType.Add(typeof(T), typeof(T));
var done = instantiatedServices.TryAdd(typeof(T), Implementation());
Debug.Assert(done, "Cannot add current service: " + typeof(T));
}
public T GetService<T>()
{
if (!instantiatedServices.ContainsKey(typeof(T)))
{
try
{
ConstructorInfo constructor = servicesType[typeof(T)].GetConstructor(new Type[0]);
Debug.Assert(constructor != null, "Cannot find a suitable constructor for " + typeof(T));
T service = (T)constructor.Invoke(null);
instantiatedServices.Add(typeof(T), service);
}
catch (KeyNotFoundException)
{
throw new ApplicationException("The requested service is not registered");
}
}
return (T)instantiatedServices[typeof(T)];
}
}
-------------------------------------------------------------------------------------------
public interface IService
{
void SayHello();
}
public class ServiceA : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from A");
}
}
public class ServiceB : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from B");
}
}
public class ServiceC : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from C");
}
}
public class ServiceD : IService
{
private readonly IService _service;
public ServiceD(IService service)
{
_service = service;
}
public void SayHello()
{
Console.WriteLine("-------------");
_service.SayHello();
Console.WriteLine("Hello,I'm from D");
}
}
-------------------------------------------------------------------------------------------
class Program
{
static void Main(string[] args)
{
IServiceLocator locator = ServiceLocator.GetInstance();
locator.AddService<ServiceA>();
locator.AddService<ServiceB>();
locator.AddService<ServiceC>();
locator.GetService<ServiceA>().SayHello();
locator.GetService<ServiceB>().SayHello();
locator.GetService<ServiceC>().SayHello();
locator.AddService(() => new ServiceD(locator.GetService<ServiceA>()));
locator.GetService<ServiceD>().SayHello();
}
}
程式輸出如下圖所示:
當我們需要註冊的服務對應的有參構造函數中的參數不需要註冊到 ServiceLocator,那我們可以採用這種方法進行服務註冊,比較靈活。
- 隱式構造
public class ServiceLocator : IServiceLocator
{
private static ServiceLocator _instance;
private static readonly object _locker = new object();
public static ServiceLocator GetInstance()
{
if (_instance == null)
{
lock (_locker)
{
if (_instance == null)
{
_instance = new ServiceLocator();
}
}
}
return _instance;
}
private readonly IDictionary<Type, Type> servicesType;
private readonly IDictionary<Type, object> instantiatedServices;
private ServiceLocator()
{
servicesType = new ConcurrentDictionary<Type, Type>();
instantiatedServices = new ConcurrentDictionary<Type, object>();
}
public void AddService<T>()
{
servicesType.Add(typeof(T), typeof(T));
}
public void AddService<T>(Func<T> Implementation)
{
servicesType.Add(typeof(T), typeof(T));
var done = instantiatedServices.TryAdd(typeof(T), Implementation());
Debug.Assert(done, "Cannot add current service: " + typeof(T));
}
public T GetService<T>()
{
var service = (T)GetService(typeof(T));
if (service == null)
{
throw new ApplicationException("The requested service is not registered");
}
return service;
}
/// <summary>
/// 關鍵代碼
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private object GetService(Type type)
{
if (!instantiatedServices.ContainsKey(type))
{
try
{
ConstructorInfo constructor = servicesType[type].GetTypeInfo().DeclaredConstructors
.Where(constructor => constructor.IsPublic).FirstOrDefault();
ParameterInfo[] ps = constructor.GetParameters();
List<object> parameters = new List<object>();
for (int i = 0; i < ps.Length; i++)
{
ParameterInfo item = ps[i];
bool done = instantiatedServices.TryGetValue(item.ParameterType, out object parameter);
if (!done)
{
parameter = GetService(item.ParameterType);
}
parameters.Add(parameter);
}
object service = constructor.Invoke(parameters.ToArray());
instantiatedServices.Add(type, service);
}
catch (KeyNotFoundException)
{
throw new ApplicationException("The requested service is not registered");
}
}
return instantiatedServices[type];
}
}
-------------------------------------------------------------------------------------------
public interface IService
{
void SayHello();
}
public class ServiceA : IService
{
public void SayHello()
{
Console.WriteLine("Hello,I'm from A");
}
}
public class ServiceD : IService
{
private readonly ServiceA _service;
public ServiceD(ServiceA service)
{
_service = service;
}
public void SayHello()
{
Console.WriteLine("-------------");
_service.SayHello();
Console.WriteLine("Hello,I'm from D");
}
}
-------------------------------------------------------------------------------------------
class Program
{
static void Main(string[] args)
{
IServiceLocator locator = ServiceLocator.GetInstance();
locator.AddService<ServiceD>();
locator.AddService<ServiceA>();
locator.GetService<ServiceD>().SayHello();
locator.GetService<ServiceA>().SayHello();
}
}
程式輸入如下圖所示:
通過隱式構造的方式可以將我們待註冊的服務依據其對應的構造函數參數類型來動態創建,這和
DotNetCore
中的 ServiceProvider 的方式很相似,它不依賴於我們服務的註冊順序,都能正常的進行構造。
官方實現
上面我們通過自己手動實現了一個 ServiceLocator 大致明白了其中的實現思路,所以有必要看一下官方是如何實現的。
首先,在使用方式上,我們一般這麼使用,示例代碼如下所示:
var services= new ServiceCollection();
......
services.AddSingleton(Configuration.GetSection(nameof(AppSettings)).Get<AppSettings>());
......
ServiceProvider serviceProvider = services.BuildServiceProvider();
可以看到,最終我們是通過一個 ServiceProvider 來獲取我們的服務提供對象,該類對應官方的源碼實現如下圖所示:
通過源碼我們不難看出,所有的服務對象都是註冊進了一個 IServiceProviderEngine 類型的對象,而該對象的具體類型又是根據 ServiceProviderOptions 的方式來進行創建。這裡,有兩個類我們需要著重註意一下:
- ServiceProvider
- CallSiteFactory
前者負責上層註入,後者負責底層構建,所以如果你想看一下官方是如何實例化這些註入對象的話可以看一下對應的實現代碼。
總結
如果你看完了我上面所有的代碼示例,回頭想想,其實一點都不難,要是自己寫的話,也是可以寫出來的。但是在實際工作中,能夠活學活用的人卻很少,歸根到底就是思維方式的問題。官方也是通過反射來實現的,只不過他的內部邏輯會更嚴謹一些,這就導致了他的實現會更複雜一些,這也是里所當然的事情。
題外話
CLICK ME(玻璃心請勿點擊)
本來我不是太想說這件事情,但是想想還是有必要說一下。
- 首先,我這人最煩 高級黑 和 小粉紅。在最開始的那幾年,我天真地以為技術圈和其他行業比起來,要相對好一些,因為這個圈子裡都是搞技術的朋友。可過了幾年之後,我的這種想法確實被打臉了。所以我現在看到一些事,遇到一些人,多少感覺挺悲哀的。說實話,這幾年,我遇到過很多 偽程式員,他們大多喜歡 拿來主義,從來不會自己 主動研究問題。看見別人做什麼,自己就跟著做什麼,遇到不會的,搞不定的要麼甩鍋,要麼放過。如果他來 請教 你,你對他說瞭解決思路,然後讓他自己花時間研究研究,他就不高興了,會給你 貼標簽。對此我想說的是,當你走出校門步入社會開始工作後,除了你父母外,沒有任何人有責任和義務免費給你進行專門指導,學習是自己的事情,一切都是事在人為。也罷,我也懶得解釋,你開心就好。
- 其次,我個人覺得抄代碼這件事情不丟人。自己經驗不足,看看老前輩之前優秀的代碼,拿來學習學習,理解並明白前輩這樣設計的優缺點,並把它轉化為自己的。當以後再遇到一些業務場景能夠活學活用就可以了,這種行為我不反對,並且我個人一直都是這樣的。但是有一點我接受不了,抄襲但不註明出處的行為,只要你的文章靈感有來源於其他地方,無論質量如何,請務必引入相關出處,否則這和剽竊沒什麼區別。我當時在博客園看到一篇首頁文章的標題和內容後真不知道該說什麼。這裡我就不指出該文章的鏈接,還望好自為之。總之,希望每一個技術人不要把寫博客這件事情玩變味了。
共勉!