2017 06 28 更新: OptionsSnapshot 已改為 OptionsManager "變更詳情" IOptionsCache 已改為 IOptionsMonitorCache "變更詳情" 在 "上一章" 中,介紹了 IOptions 的使用, 而我們知道,在 Configurati ...
2017-06-28 更新:
OptionsSnapshot 已改為 OptionsManager 變更詳情
IOptionsCache 已改為 IOptionsMonitorCache 變更詳情
在 上一章 中,介紹了 IOptions 的使用, 而我們知道,在 ConfigurationBuilder 的 AddJsonFile 中,有一個 reloadOnChange 參數,設置為 true 時,在配置文件發生變化時,會自動更新 IConfigurationRoot ,這是一個非常棒的特性,遺憾的是 IOptions 在配置源發生變化時,並不會進行更新。好在,微軟還為我們提供了 IOptionsSnapshot ,本章就來探索一下其源碼。
IOptionsSnapshot
IOptionsSnapshot 繼承自IOptions
,並擴展了一個Get
方法:
public interface IOptionsSnapshot<out TOptions> : IOptions<TOptions> where TOptions : class, new()
{
TOptions Get(string name);
}
看到Get
方法的Name
參數,我想大家便會想到在 第一章 中所介紹的指定Name
的Configure
方法,這便是它的用武之地了,通過Name
的不同,來配置同一Options
類型的多個實例。
那 IOptionsSnapshot 又是如何實現配置的同步的呢?別急,先看一下它與 IOption 的區別:
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsSnapshot<>)));
首先很明顯的是:一個是單例,一個指定作用域。其次,IOptionsSnapshot 的實現者是 OptionsSnapshot
。
OptionsSnapshot
從名字上來看,便知道它保存的只是一份快照,先看下源碼:
public class OptionsSnapshot<TOptions> : IOptionsSnapshot<TOptions> where TOptions : class, new()
{
private readonly IOptionsFactory<TOptions> _factory;
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();
public OptionsSnapshot(IOptionsFactory<TOptions> factory)
{
_factory = factory;
}
public TOptions Value => Get(Options.DefaultName);
public virtual TOptions Get(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
// Store the options in our instance cache
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
}
代碼很簡單,Options 的創建是通過 IOptionsFactory
來實現的,而 Options 的實例是通過 OptionsCache
來保存的。那便去看下他們的源碼。
IOptionsFactory
public interface IOptionsFactory<TOptions> where TOptions : class, new()
{
TOptions Create(string name);
}
public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
{
_setups = setups;
_postConfigures = postConfigures;
}
public TOptions Create(string name)
{
var options = new TOptions();
foreach (var setup in _setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
{
namedSetup.Configure(name, options);
}
else if (name == Options.DefaultName)
{
setup.Configure(options);
}
}
foreach (var post in _postConfigures)
{
post.PostConfigure(name, options);
}
return options;
}
}
IOptionsFactory 的預設實現類是 OptionsFactory
,在創建Options
時,先執行所有的Configure
方法,然後執行PostConfigure
方法。在 第一章 中講的PostConfigure
方法,也在這裡派上用場了。
OptionsCache
OptionsCache 用來緩存 Options 的實例,相當於一個優化的Options
字典,並使用Lazy
實現了延遲初始化,看代碼:
public class OptionsCache<TOptions> : IOptionsCache<TOptions> where TOptions : class
{
private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);
public virtual TOptions GetOrAdd(string name, Func<TOptions> createOptions)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (createOptions == null) throw new ArgumentNullException(nameof(createOptions));
return _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions)).Value;
}
public virtual bool TryAdd(string name, TOptions options)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (options == null) throw new ArgumentNullException(nameof(options));
return _cache.TryAdd(name, new Lazy<TOptions>(() => options));
}
public virtual bool TryRemove(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
return _cache.TryRemove(name, out var ignored);
}
}
總結
IOptionsSnapshot 通過註冊為一個作用域內的單例模式,來保證當配置發生變化時,下一個請求可以獲取到最新的配置。其實在 2.0-preview2
中,OptionsSnapshot 使用了 IOptionsChangeTokenSource 模式,來監聽配置的變化,當發生變化清空 OptionsCache 中的緩存,來實現 Options 的自動更新。當時我還感到困擾:“OptionsSnapshot既然能夠做到配置的更新,怎麼還註冊成Scope
實例呢?”。而現在缺少了 IOptionsChangeTokenSource 模式的即時更新,或許讓我們感覺不是那麼爽,當然也有一個致命的問題,就是當我們在一個自定義的類中使用了 IOptionsSnapshot ,並且這個類本身是以單例的形式註冊的,那麼便永遠獲取不到最新的配置了。不過,我們還有最後一個大殺器: IOptionsMonitor,來滿足我們極致的需求,哈哈,下一篇就來介紹一下它。