前言
在 ASP.NET Core 中,微軟提供了一套默認的依賴注入實現(xiàn),該實現(xiàn)對應的包為:
Microsoft.Extensions.DependencyInjection
,我們可以通過查看其對應的開源倉庫看一下它的具體實現(xiàn)?;谠搶崿F(xiàn),我們不必顯式創(chuàng)建我們的服務對象,可以將其統(tǒng)一注入到ServiceProvider
中進行集中維護,使用的時候直接在該對象中獲取即可。讓我們在編寫業(yè)務邏輯時,不用太關注對象的創(chuàng)建和銷毀。這也是為什么現(xiàn)在有些最佳實踐中建議不要過多使用New
的方式來獲取對象。在本文中,我們將一起了解一下如何實現(xiàn)一個自己的ServiceProvider。
自己動手,豐衣足食
為了方便區(qū)分,我這里自定義定義的類叫:ServiceLocator,其功能與官方的 ServiceProvider 類似。
基本實現(xiàn)
首先,我們需要定義一個簡單的服務發(fā)現(xiàn)接口,用于約束上層具體的實現(xiàn),示例代碼如下所示:
public interface IServiceLocator { void AddService<T>(); T GetService<T>(); }
接著,我們定義一個繼承上述接口的具體實現(xiàn)類,示例代碼如下所示:
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"); } } }
這樣,我們就實現(xiàn)了一個最基本的服務發(fā)現(xiàn)類,通過該類,我們可以將我們的多個服務進行集中管理。這里我為了方便,模擬了 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(); } }
程序運行效果如下圖所示:
程序看起來運行不錯,結果也符合我們的預期。但是稍微有點工作經(jīng)驗的朋友就會發(fā)現(xiàn)上述的實現(xiàn)是有很多潛在問題的。對于 IServiceLocator
的實例,我們一般會以單例模式來進行使用,這就會設計到線程安全的委托,所以我們的服務列表必須要是線程安全的。此外,如果我們需要注冊的服務過多,通過上述方式來進行注冊的話會加到系統(tǒng)開銷,因為我們的服務一旦注冊進去就會立刻被初始化,從而耗費不必要的系統(tǒng)內(nèi)存,所以我們應該讓其實例化推遲,在使用的時候才進行實例化操作。下面我們對上述問題一一進行改進。
單例模式
單例模式是一種最簡單也是使用最頻繁的設計模式,單例模式本身也有很多形式,感興趣的可以查看我之前的博文:設計模式系列 - 單例模式
<https://www.cnblogs.com/hippieZhou/p/9940021.html>,這里,我采用 線程安全 方式來修改我們的
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"); } } }
延遲加載
要想讓所有的注冊的服務支持懶加載,我們需要引入一個新的集合,這個新的集合是用于存儲我們相應的實例對象,在注冊的時候我們只記錄注冊類型,在需要訪問到相應的服務時,我們只需要在這個實例集合列表中訪問,如果發(fā)現(xiàn)我們需要的服務還未被實例化,那我們再進行實例化,然后將該實例化對象存儲起來并返回。對于用哪種數(shù)據(jù)結構來存,我們可以采用多種數(shù)據(jù)結構,我這里仍然采用字典來存儲,示例代碼如下所示:
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)]; } }
自匹配構造
上面的所有改進都支持無參構造函數(shù)的服務,但是對于有參構造函數(shù)的服務注冊,我們定義的
服務提供者就不滿足的,因為上述的反射方式是不支持有參構造函數(shù)的。對于這種情況我們有兩種解決辦法。第一種是將服務的初始化放到最上層,然后
ServiceLocator 通過一個Fun 的方式來獲取該示例,并存儲起來,我們稱之為 顯示創(chuàng)建
。第二種方式依然是通過反射方式,只是這個反射可能會復雜一下,我們稱之為隱式創(chuàng)建。我們分別對于這兩個實現(xiàn)方式進行代碼示例。
* 顯示構造 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)); } //新增接口對應的具體實現(xiàn) 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(); } }
程序輸出如下圖所示:
當我們需要注冊的服務對應的有參構造函數(shù)中的參數(shù)不需要注冊到 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(); } }
程序輸入如下圖所示:
通過隱式構造的方式可以將我們待注冊的服務依據(jù)其對應的構造函數(shù)參數(shù)類型來動態(tài)創(chuàng)建,這和 DotNetCore 中的 ServiceProvider
的方式很相似,它不依賴于我們服務的注冊順序,都能正常的進行構造。
官方實現(xiàn)
上面我們通過自己手動實現(xiàn)了一個 ServiceLocator 大致明白了其中的實現(xiàn)思路,所以有必要看一下官方是如何實現(xiàn)的。
首先,在使用方式上,我們一般這么使用,示例代碼如下所示:
var services= new ServiceCollection(); ......
services.AddSingleton(Configuration.GetSection(nameof(AppSettings)).Get<AppSettings>());
...... ServiceProvider serviceProvider = services.BuildServiceProvider();
可以看到,最終我們是通過一個 ServiceProvider 來獲取我們的服務提供對象,該類對應官方的源碼實現(xiàn)如下圖所示:
通過源碼我們不難看出,所有的服務對象都是注冊進了一個 IServiceProviderEngine 類型的對象,而該對象的具體類型又是根據(jù)
ServiceProviderOptions 的方式來進行創(chuàng)建。這里,有兩個類我們需要著重注意一下:
* ServiceProvider
* CallSiteFactory
前者負責上層注入,后者負責底層構建,所以如果你想看一下官方是如何實例化這些注入對象的話可以看一下對應的實現(xiàn)代碼。
總結
如果你看完了我上面所有的代碼示例,回頭想想,其實一點都不難,要是自己寫的話,也是可以寫出來的。但是在實際工作中,能夠活學活用的人卻很少,歸根到底就是思維方式的問題。官方也是通過反射來實現(xiàn)的,只不過他的內(nèi)部邏輯會更嚴謹一些,這就導致了他的實現(xiàn)會更復雜一些,這也是里所當然的事情。
題外話
CLICK ME(玻璃心請勿點擊)
本來我不是太想說這件事情,但是想想還是有必要說一下。
* 首先,我這人最煩 高級黑 和 小粉紅
。在最開始的那幾年,我天真地以為技術圈和其他行業(yè)比起來,要相對好一些,因為這個圈子里都是搞技術的朋友??蛇^了幾年之后,我的這種想法確實被打臉了。所以我現(xiàn)在看到一些事,遇到一些人,多少感覺挺悲哀的。說實話,這幾年,我遇到過很多
偽程序員,他們大多喜歡 拿來主義,從來不會自己 主動研究問題??匆妱e人做什么,自己就跟著做什么,遇到不會的,搞不定的要么甩鍋,要么放過。如果他來 請教
你,你對他說了解決思路,然后讓他自己花時間研究研究,他就不高興了,會給你貼標簽
。對此我想說的是,當你走出校門步入社會開始工作后,除了你父母外,沒有任何人有責任和義務免費給你進行專門指導,學習是自己的事情,一切都是事在人為。也罷,我也懶得解釋,你開心就好。
*
其次,我個人覺得抄代碼這件事情不丟人。自己經(jīng)驗不足,看看老前輩之前優(yōu)秀的代碼,拿來學習學習,理解并明白前輩這樣設計的優(yōu)缺點,并把它轉化為自己的。當以后再遇到一些業(yè)務場景能夠活學活用就可以了,這種行為我不反對,并且我個人一直都是這樣的。但是有一點我接受不了,抄襲但不注明出處的行為,只要你的文章靈感有來源于其他地方,無論質(zhì)量如何,請務必引入相關出處,否則這和剽竊沒什么區(qū)別。我當時在博客園看到一篇首頁文章的標題和內(nèi)容后真不知道該說什么。這里我就不指出該文章的鏈接,還望好自為之??傊?,希望每一個技術人不要把寫博客這件事情玩變味了。
共勉!
相關參考
* Service Locator Pattern in C#
<http://www.stefanoricciardi.com/2009/09/25/service-locator-pattern-in-csharpa-simple-example/>
* Microsoft.Extensions.DependencyInjection
<https://github.com/aspnet/Extensions>
熱門工具 換一換