跟我一起学.NetCore之选项(Options)核心类型简介
前言
.NetCore中提供的選項框架,我把其理解為配置組,主要是將服務中可供配置的項提取出來,封裝成一個類型;從而服務可根據應用場景進行相關配置項的設置來滿足需求,其中使用了依賴注入的形式,使得更加簡單、便捷;另外和配置(Configuration)系統的無縫結合,使得服務更加靈活;而對于Options我們經常在注冊服務中用到,相信只要接觸過.NetCore中的小伙伴都知道,在注冊服務的時候,經常在參數中進行Options的配置(如下圖),可以直接的說:沒有Options的服務不是好服務~~~
正文
Options模型中主要有三個核心接口類型:IOption<TOptions>、IOptionsSnapshot<TOptions>、IOptionsMonitor<TOptions>,這里的TOptions就是指針對服務提取出來的配置項封裝的類型,以下分別看看三個核心類型定義了什么?
IOption<TOptions>
namespace Microsoft.Extensions.Options {//?這里TOptions?做了一個約束,必須有無參構造函數public interface IOptions<out TOptions> where TOptions : class, new(){//?這里是通過屬性的方式定義TOptions????TOptions Value{get;}} }IOptionsSnapshot<TOptions>
namespace Microsoft.Extensions.Options {???// 這里TOptions 做了一個約束,必須有無參構造函數public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new(){//?通過名字獲取?TOptionsTOptions Get(string name);} }IOptionsMonitor<TOptions>
namespace Microsoft.Extensions.Options {public interface IOptionsMonitor<out TOptions>{//?通過屬性獲取TOptions????TOptions CurrentValue{get;}//?通過名稱獲取TOptionsTOptions Get(string name);//?這是用于監聽改變的,如果數據設置項改變,就會發出通知IDisposable OnChange(Action<TOptions, string> listener);} }
通過以上三種類型的定義,大概應該知道TOptions有對應的名字,根據對應的名字創建或獲取TOptions,可能會問,IOption中是通過屬性獲取的,沒有指定名字啊,其實是有的,只是名字默認為空,所以稱之為默認Option;而對于IOptionsMonitor一看便知,它提供了監聽改變的功能,所以后續如果需要監聽改變,就可以用這個類型接口;除此,微軟為三個核心類型提供了默認實現,IOptions<TOptions>和IOptionsSnapshot<TOptions>的默認實現為OptionsManager<TOptions>,IOptionsMonitor<TOptions>的默認實現為OptionsMonitor<TOptions>,來、走進他們的世界,看看是如何實現的(進行簡單的注釋):
OptionsManager<TOptions>
//?實現了IOptions<TOptions>?和IOptionsSnapshot<TOptions>, 同時也約束了TOptions public class OptionsManager<TOptions> :IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new() {//?用于專門提供TOptions實例的,同時也對TOpions進行相關初始化private readonly IOptionsFactory<TOptions> _factory;//?提高性能,將對應的TOptions實例進行緩存private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // 構造函數,通過依賴注入的形式,將factory進行注入public OptionsManager(IOptionsFactory<TOptions> factory) {_factory = factory;}//?實現IOptions<TOptions>通過屬性獲取TOptions實例public TOptions Value{get{// 這里通過一個默認的名字獲取,只是這個名字默認為空,所以還是有名字的return Get(Options.DefaultName);}}?//?實現IOptionsSnapshot<TOptions>通過名字獲取TOptions?public virtual TOptions Get(string name) {name = name ?? Options.DefaultName;//?如果緩存中沒有,就通過傳入的Action進行創建并加入到緩存中return _cache.GetOrAdd(name, () => _factory.Create(name));} } //?定義的?TOptions默認名稱 public static class Options {public static readonly string DefaultName = string.Empty; }OptionsMonitor<TOptions>
namespace Microsoft.Extensions.Options {//?實現IOptionsMonitor?,對TOpitons?進行約束public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable where TOptions : class, new(){//?根據名稱緩存TOptions對象private readonly IOptionsMonitorCache<TOptions> _cache;//?用于創建TOptions對象private readonly IOptionsFactory<TOptions> _factory;//?監聽改變的核心private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;private readonly List<IDisposable> _registrations = new List<IDisposable>();//?改變觸發的事件internal event Action<TOptions, string> _onChange;// 構造函數public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache){_factory = factory;_sources = sources;_cache = cache;//?注冊改變回調,用于監聽foreach (var source in _sources){var registration = ChangeToken.OnChange(() => source.GetChangeToken(),(name) => InvokeChanged(name),source.Name);_registrations.Add(registration);}}//?這個方法是重點,如果發生改變,移除之前的TOptions對象,重新創建一個新TOptionsprivate void InvokeChanged(string name){name = name ?? Options.DefaultName;//?根據名字移除TOpitons對象_cache.TryRemove(name);//?重新根據名稱獲取對象var options = Get(name);if (_onChange != null){_onChange.Invoke(options, name);}}//?獲取默認的TOptions對象public TOptions CurrentValue{get => Get(Options.DefaultName);}//?根據名稱獲取TOptions對象,如果沒有就利用OptionsFactory創建TOptions對象public virtual TOptions Get(string name){name = name ?? Options.DefaultName;//?return _cache.GetOrAdd(name, () => _factory.Create(name));}// 注冊監聽改變的蝙蝠public IDisposable OnChange(Action<TOptions, string> listener){var disposable = new ChangeTrackerDisposable(this, listener);_onChange += disposable.OnChange;return disposable;}//?取消注冊的監聽改變回調,同時移除對應的監聽Tokenpublic void Dispose(){foreach (var registration in _registrations){registration.Dispose();}_registrations.Clear();}//?內部類internal class ChangeTrackerDisposable : IDisposable{private readonly Action<TOptions, string> _listener;private readonly OptionsMonitor<TOptions> _monitor;public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener){_listener = listener;_monitor = monitor;}//?觸發改變時調用對應注冊的回調public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);//?Dispose??方法進行解除注冊的監聽回調public void Dispose() => _monitor._onChange -= OnChange;}} }通過以上代碼段得知,IOptions<TOptions>和IOptionsSnapshot<TOptions>其實本質都是一樣的,都是命名的Options,統一由IOptionsFactory創建提供TOptions對象;而對于IOptionsMonitor<TOptions>,?監聽的本質是通過IOptionsChangeTokenSource<TOptions>這個實現,每次監聽到改變都把對應名稱的TOptions對象移除,重新創建一個新的TOptions對象,從而獲取到最新的值,其實最終改變通知的本質還是使用IChangeToken進行通知,這個可以參考之前配置的監聽改變(參考這篇:跟我一起學.NetCore之配置變更監聽);
本想看完以上默認實現,就打算進行舉例演示了,不再深入看代碼;但是相信看到這的小伙伴肯定會問:IOptionsFactory<TOptions>是怎么創建出TOptions的??重點都不說,差評^_^~~~~~,其實我也是這么想的,所以繼續再看看IOptionsFactory<TOptions>;
IOptionsFactory<TOptions>的默認實現是OptionsFactory<TOptions>;創建TOptions我理解為三步驟,創建對象->加工對象(初始化)->驗證(驗證合法性):
namespace Microsoft.Extensions.Options {public interface IOptionsFactory<TOptions> where TOptions : class, new(){// 接口中定義了一個創建方法,用于創建TOptionsTOptions Create(string name);} } namespace Microsoft.Extensions.Options {//?實現IOptionsFactory接口,并約束TOptionspublic class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new(){//?初始化邏輯,初始化由IConfigureOptions和IPostConfigureOptions處理private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;// 驗證邏輯private readonly IEnumerable<IValidateOptions<TOptions>> _validations;//?構造函數 public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null){ }// 構造函數 public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations){_setups = setups;_postConfigures = postConfigures;_validations = validations;}//?創建TOptions的核心方法,傳入名稱,如果沒名稱,默認是空public TOptions Create(string name){//?1?根據傳入的TOptions創建對象,這里用無參構造函數,所以之前需要對TOptions進行約束var options = new TOptions();// 2 初始化foreach (var setup in _setups){//?根據傳入的名字是否為默認名稱選擇不同的加工方法if (setup is IConfigureNamedOptions<TOptions> namedSetup){namedSetup.Configure(name, options);}else if (name == Options.DefaultName){setup.Configure(options);}}//?IPostConfigureOptions對Options加工foreach (var post in _postConfigures){post.PostConfigure(name, options);}//?進行驗證,?如果不傳入驗證規則,則代表不進行驗證if (_validations != null){//?存放驗證失敗的錯誤消息var failures = new List<string>();// 遍歷驗證foreach (var validate in _validations){// 進行驗證var result = validate.Validate(name, options);// 如果驗證失敗if (result.Failed){ //?將驗證失敗錯誤信息加入到列表中failures.AddRange(result.Failures);}}//?如果驗證失敗,就拋出異常OptionsValidationExceptionif (failures.Count > 0){throw new OptionsValidationException(name, typeof(TOptions), failures);}}// 返回實例對象return options;}} }對于TOptions的創建邏輯就暫時先看到這吧,如果需要再詳細了解具體邏輯,可以私下進行研究;?
總結
哎呀,這篇先不一一舉例演示了,可能導致篇幅過長,上個WC的時間估計看不完(哈哈哈);那么就是單純的代碼說明嗎?不僅僅如此,這篇主要講解代碼的同時,其實著重凸顯了IOption<TOptions>、IOptionsSnapshot<TOptions>、IOptionsMonitor<TOptions>三個核心類型,然后圍繞三個核心類型簡單看了內部實現、創建過程和監聽邏輯,因為在實際應用也是圍繞以上進行使用和擴展的,最終的目的是讓使用不再糊涂,其實這才是終極目標啦~~~~?下一篇就專門針對Options舉例演示!!!
----------------------------------------------
一個被程序搞丑的帥小伙,關注"Code綜藝圈",識別關注跟我一起學~~~
總結
以上是生活随笔為你收集整理的跟我一起学.NetCore之选项(Options)核心类型简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 都在讨论高并发,结果连并发量、TPS、Q
- 下一篇: 《ASP.NET Core项目开发实战入