ASP.NET Core 中的 ServiceProvider

来源:https://www.cnblogs.com/hippieZhou/archive/2019/08/28/11401267.html
-Advertisement-
Play Games

前言 在 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(玻璃心請勿點擊)

本來我不是太想說這件事情,但是想想還是有必要說一下。

  • 首先,我這人最煩 高級黑小粉紅。在最開始的那幾年,我天真地以為技術圈和其他行業比起來,要相對好一些,因為這個圈子裡都是搞技術的朋友。可過了幾年之後,我的這種想法確實被打臉了。所以我現在看到一些事,遇到一些人,多少感覺挺悲哀的。說實話,這幾年,我遇到過很多 偽程式員,他們大多喜歡 拿來主義,從來不會自己 主動研究問題。看見別人做什麼,自己就跟著做什麼,遇到不會的,搞不定的要麼甩鍋,要麼放過。如果他來 請教 你,你對他說瞭解決思路,然後讓他自己花時間研究研究,他就不高興了,會給你 貼標簽。對此我想說的是,當你走出校門步入社會開始工作後,除了你父母外,沒有任何人有責任和義務免費給你進行專門指導,學習是自己的事情,一切都是事在人為。也罷,我也懶得解釋,你開心就好。
  • 其次,我個人覺得抄代碼這件事情不丟人。自己經驗不足,看看老前輩之前優秀的代碼,拿來學習學習,理解並明白前輩這樣設計的優缺點,並把它轉化為自己的。當以後再遇到一些業務場景能夠活學活用就可以了,這種行為我不反對,並且我個人一直都是這樣的。但是有一點我接受不了,抄襲但不註明出處的行為,只要你的文章靈感有來源於其他地方,無論質量如何,請務必引入相關出處,否則這和剽竊沒什麼區別。我當時在博客園看到一篇首頁文章的標題和內容後真不知道該說什麼。這裡我就不指出該文章的鏈接,還望好自為之。總之,希望每一個技術人不要把寫博客這件事情玩變味了。

共勉!

相關參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 5.2函數小高級 5.2.1 函數當參數 1 函數也可以當返回值 練習 函數其實也是一種數據類型,可以當變數存儲 面試題 5.2.2 閉包 閉包的意義: 返回的函數對象,不僅僅是一個函數對象,在該函數外還包裹了一層作用域,這使得,該函數無論在何處調用,優先使用自己外層包裹的作用域 閉包 就是在內層函 ...
  • WinForm任務欄最小化 在C#編寫的WinForm里,在FormBorderStyle設為None的時候,任務欄點擊程式圖標,不會自動最小化。在主視窗WinForm.cs裡加入如下代碼後,即可恢復該功能。 protected override CreateParams CreateParams ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 在上面實現曲線相關屬性的設置的基礎上,要能修改曲線圖的X軸以及Y軸的上限和下限。 效 ...
  • 書接上文,繼續搭建我們基於.netCore 的開發框架。首先是我們的項目分層結構。 這個分層結構,是參考張老師的分層結構,但是實際項目中,我沒有去實現倉儲模型。因為我使用的是EFCore ,最近也一直在想,EFCore 在我們的架構體系中到底扮演著什麼樣的角色?? 當然,實現倉儲層,也有他的好處,如 ...
  • import requests import json # 爬蟲原理 模擬瀏覽器 獲取請求數據 #點擊播放連接 #url = "https://www.ximalaya.com/revision/play/album?albumId=297790&pageNum=1&sort=1&pageSize= ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 在上面實現右鍵的基礎上,效果如下: 實現 添加如下代碼 方法中 效果 完整示例代碼 ...
  • 源碼下載 -> 提取碼 QQ505645074 用戶模塊 簡訊模塊 定位模塊 海圖信息 通信協議 定位協議 指令$GPSP::24 47 50 53 50 00 0F 00 00 00 01 00 00 00 3E(GPS定位設置)響應$GPSX:24 47 50 53 58 00 26 1F FA ...
  • 場景 C#窗體應用中使用ZedGraph曲線插件繪製圖表: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/99716066 在上面已經實現基本的曲線圖之後,效果如下: 當然這不是我們的效果,還要對其屬性進行設置。 但是畢竟其屬性和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...