Asp.NetCore源码学习[1-1]:配置[Configuration]
在Asp. NetCore中,配置系统支持不一样的配置源(文件、环境变量等),虽然有多种的配置源,可是最终提供给系统使用的只有一个对象,那就是
ConfigurationRoot
。其内部维护了一个集合,用于保存各类配置源的IConfigurationProvider
。IConfigurationProvider
提供了对配置源的实际访问。当经过key去ConfigurationRoot
查找对应的Value时,实际上会经过遍历IConfigurationProvider
去查找对应的键值。 本篇文章主要描述ConfigurationRoot
对象的构建过程。git
Asp.NetCore
入口点代码CreateWebHostBuilder(args).Build().Run();
Asp.NetCore
部分源码WebHostBuilder
内部维护了_configureAppConfigurationBuilder
字段,其类型是 Action<WebHostBuilderContext, IConfigurationBuilder>
,该委托用于对ConfigurationBuilder
进行配置。首先在构造函数中先将环境变量的配置加载到 _config
字段中,用于设置默认监听目录为程序执行目录。CreateDefaultBuilder
方法中经过调用ConfigureAppConfiguration
方法保存委托,而后在Build
方法中构建配置系统目标类ConfigurationRoot
,最后经过单例模式注入到依赖系统中。github
public class WebHostBuilder { private Action<WebHostBuilderContext, IConfigurationBuilder> _configureAppConfigurationBuilder; private IConfiguration _config; public WebHostBuilder() { _hostingEnvironment = new HostingEnvironment(); /// _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); } public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate) { _configureAppConfigurationBuilder += configureDelegate; return this; } public IWebHost Build() { var builder = new ConfigurationBuilder(); //经过委托配置IConfigurationBuilder _configureAppConfigurationBuilder?.Invoke(_context, builder); //构建ConfigurationRoot var configuration = builder.Build(); // register configuration as factory to make it dispose with the service provider services.AddSingleton<IConfiguration>(_ => configuration); } } public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); builder.ConfigureAppConfiguration((hostingContext, config) => { //为 IConfigurationBuilder 注册配置源(JsonConfigurationSource) config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); }); return builder; }
Asp.NetCore
代码,写静态测试方法public class ConfigurationTest { public static void Run() { //1.实例化ConfigurationBuilder var builder = new ConfigurationBuilder(); //2.增长配置源 builder.AddJsonFile(null, "appsettings.json", true,true); //3.构建ConfigurationRoot对象 var configuration = builder.Build(); //观察ConfigurationRoot是否发生更改 Task.Run(() => { ChangeToken.OnChange(() => configuration.GetReloadToken(), () => { Console.WriteLine("Configuration has changed"); }); }); Thread.Sleep(60000); } }
ConfigurationBuilder
类构建目标类ConfigurationRoot
ConfigurationBuilder
是配置系统的构建类,经过Build
方法构建配置系统的目标类ConfigurationRoot
。其维护了一个用于保存IConfigurationSource
的集合,IConfigurationSource
用于提供IConfigurationProvider
。在Build
方法中,遍历IList
构建IConfigurationProvider
对象,而后将IConfigurationProvider
集合传到ConfigurationRoot
的构造函数中。代码以下:json
/// <summary> /// 配置系统构建类 /// </summary> public class ConfigurationBuilder : IConfigurationBuilder { /// 配置源集合 public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); /// 增长一个新的配置源 public IConfigurationBuilder Add(IConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Sources.Add(source); return this; } /// 经过配置源中提供的IConfigurationProvider构建配置根对象ConfigurationRoot public IConfigurationRoot Build() { var providers = new List<IConfigurationProvider>(); foreach (var source in Sources) { var provider = source.Build(this); providers.Add(provider); } return new ConfigurationRoot(providers); } }
IConfigurationSource
对象不单单用于建立IConfigurationProvider
,还保存了构建IConfigurationProvider
须要的依赖和配置选项。并发
ConfigurationRoot
类实现该类经过IList
进行初始化。其内部维护了类型为ConfigurationReloadToken
的字段,该字段提供给外部,来进行全部配置源的监听。每一个IConfigurationProvider
对象一样维护了类型为ConfigurationReloadToken
的字段。当IConfigurationProvider
监测到配置源发生更改时,更改IConfigurationProvider.IChangeToken
的状态
在构造函数中执行如下操做:app
IConfigurationProvider.Load()
从配置源(文件、环境变量等)加载配置项ChangeToken.OnChange()
方法 监听每一个IConfigurationProvider.IChangeToken
的状态改变,当其状态发生改变时更改ConfigurationRoot.IChangeToken
的状态。(在ConfigurationRoot
外部能够经过监听IChangeToken状态的改变,得知配置源发生了改变)/// <summary> /// 配置系统的根节点 /// </summary> public class ConfigurationRoot : IConfigurationRoot, IDisposable { private readonly IList<IConfigurationProvider> _providers; private readonly IList<IDisposable> _changeTokenRegistrations; private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); /// <summary> /// 使用IConfigurationProvider集合初始化ConfigurationRoot /// </summary> /// <param name="providers">The <see cref="IConfigurationProvider"/>s for this configuration.</param> public ConfigurationRoot(IList<IConfigurationProvider> providers) { _providers = providers ?? throw new ArgumentNullException(nameof(providers)); _changeTokenRegistrations = new List<IDisposable>(providers.Count); foreach (var p in providers) { p.Load(); //将每一个IConfigurationProvider的change token与ConfigurationRoot 的change token绑定 //当IConfigurationProvider._cts.Cancel()触发时,触发当ConfigurationRoot._cts.Cancel() _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged())); } } public IEnumerable<IConfigurationProvider> Providers => _providers; /// 遍历_providers来设置、获取配置项的键值对 public string this[string key] { get { for (var i = _providers.Count - 1; i >= 0; i--) { var provider = _providers[i]; if (provider.TryGet(key, out var value)) { return value; } } return null; } set { if (!_providers.Any()) { throw new InvalidOperationException("Can't find any IConfigurationProvider"); } foreach (var provider in _providers) { provider.Set(key, value); } } } /// 获取IChangeToken,用于供外部使用者收到配置改变的消息通知 public IChangeToken GetReloadToken() => _changeToken; public void Reload() { foreach (var provider in _providers) { provider.Load(); } RaiseChanged(); } /// 生成一个新的change token,并触发ConfigurationRoot的change token(旧)状态改变 private void RaiseChanged() { var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); } /// <inheritdoc /> public void Dispose() { // dispose change token registrations foreach (var registration in _changeTokenRegistrations) { registration.Dispose(); } // dispose providers foreach (var provider in _providers) { (provider as IDisposable)?.Dispose(); } } }
ConfigurationReloadToken
的实现其使用适配器模式,经过CancellationTokenSource
实现IChangeToken
接口。代码以下:asp.net
/// <summary> /// 用于发送更改通知 /// </summary> public interface IChangeToken { /// 指示是否发生更改 bool HasChanged { get; } /// 指示token是否会主动调用callbacks,false的状况下:token的消费者须要轮询 HasChanged 属性检测是否发生更改 bool ActiveChangeCallbacks { get; } /// 注册回调函数, 更改发生时(HasChanged为true),会被调用(只会被调用一次) IDisposable RegisterChangeCallback(Action<object> callback, object state); }
/// 基于CancellationTokenSource实现IChangeToken接口(适配器模式) public class ConfigurationReloadToken:IChangeToken { private CancellationTokenSource _cts = new CancellationTokenSource(); /// CancellationTokenSource会主动调用callbacks,因此为true public bool ActiveChangeCallbacks => true; public bool HasChanged => _cts.IsCancellationRequested; public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state); public void OnReload() => _cts.Cancel(); }
CancellationTokenSource
对象基于协做取消模式设计的对象,用于取消异步操做或者长时间同步操做。( .NET指南/取消托管线程)异步
CancellationTokenSource
对象的特色:ide
ChangeToken.OnChange
静态方法实现更改通知的持续消费因为CancellationTokenSource.Cancel只会触发一次callbacks,须要ChangeToken.OnChange来实现持续监听取消通知。
实现原理:每次须要发生更改通知时,首先生成一个新的cts,而后改变旧的cts状态,触发回调函数,最后将新的cts与回调函数绑定。函数
/// <summary> /// 将changeToken消费者注册到IChangeToken的回调函数中,并实现IChangeToken状态改变的持续消费 /// </summary> public static class ChangeToken { /// 为changetoken生产者绑定消费者. /// 1.在IChangeToken的状态未改变的状况下,生产者每次返回相同的IChangeToken /// 2.状态改变时,生产者生成新的IChangeToken,消费者执行响应动做,为新的IChangeToken绑定消费者,释放旧的IChangeToken public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer) { if (changeTokenProducer == null) { throw new ArgumentNullException(nameof(changeTokenProducer)); } if (changeTokenConsumer == null) { throw new ArgumentNullException(nameof(changeTokenConsumer)); } return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer); } private class ChangeTokenRegistration<TState> : IDisposable { private readonly Func<IChangeToken> _changeTokenProducer; private readonly Action<TState> _changeTokenConsumer; private readonly TState _state; private IDisposable _disposable;//用于保存当前正在使用的 IChangeToken private static readonly NoopDisposable _disposedSentinel = new NoopDisposable(); public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state) { _changeTokenProducer = changeTokenProducer; _changeTokenConsumer = changeTokenConsumer; _state = state; var token = changeTokenProducer(); RegisterChangeTokenCallback(token); } /// 1.先执行消费者动做,再绑定新的token,防止消费者执行并发动做 /// 2.不然的话可能出现如下状况:若是在绑定新token的回调方法后,而且在执行callback以前,新token的状态发生了改变,此时第二次callback也会执行,这样会形成callback的并发执行。 private void OnChangeTokenFired() { var token = _changeTokenProducer(); try { _changeTokenConsumer(_state); } finally { RegisterChangeTokenCallback(token); } } private void RegisterChangeTokenCallback(IChangeToken token) { var registraton = token.RegisterChangeCallback(s => ((ChangeTokenRegistration<TState>)s).OnChangeTokenFired(), this); SetDisposable(registraton); } /// 1.将当前使用的IChangeToken保存到_disposable字段中 /// 2.若是本对象已经释放,马上释放新产生的 IChangeToken /// 3.已经失效的 IChangeToken 因为已经不被引用,等待GC自动释放(为何不手动释放?) private void SetDisposable(IDisposable disposable) { // 读取当前保存的 IChangeToken var current = Volatile.Read(ref _disposable); // 若是本对象已经释放,马上释放新产生的IChangeToken if (current == _disposedSentinel) { disposable.Dispose(); return; } // 不然更新_disposable字段,返回原值 var previous = Interlocked.CompareExchange(ref _disposable, disposable, current); // current = 以前的 IChangeToken if (previous == _disposedSentinel) { // 更新失败 说明对象已释放 previous = _disposedSentinel // 本对象已经释放,马上释放新产生的IChangeToken disposable.Dispose(); } else if (previous == current) { // 更新成功 previous 是以前的 IChangeToken } else { // 若是其余人为 _disposable赋值,且值不为 _disposedSentinel // 会形成对象未释放、更新失败的状况 throw new InvalidOperationException("Somebody else set the _disposable field"); } } // 释放当前保存的 IChangeToken,将字段赋值为_disposedSentinel public void Dispose() { Interlocked.Exchange(ref _disposable, _disposedSentinel).Dispose(); } private class NoopDisposable : IDisposable { public void Dispose() { } } } }
ChangeToken.OnChange
测试方法该测试方法经过一个ChangeTokenProducer
类来模拟内部的状态改变。经过内部维护一个ConfigurationReloadToken
,能够向外部发出更改通知(一次性)。为了实现向外部持续发出更改通知,能够在更改ConfigurationReloadToken
状态以前,从新实例化有一个新的IChangeToken
,供外部从新绑定回调方法。oop
class ChangeTokenTest { public static void Run() { var ctsProducer = new ChangeTokenProducer(); var subscriber = ChangeToken.OnChange(() => ctsProducer.GetReloadToken(), () => { Console.WriteLine("消费者观察到改变事件"); }); Console.ReadLine(); } /// <summary> /// 假设该类须要在内部状态发生改变时向外界发送更改通知 /// </summary> private class ChangeTokenProducer { // cts只能执行一次相应动做 private ConfigurationReloadToken _changetoken = new ConfigurationReloadToken(); /// <summary> /// 模拟状态改变 /// </summary> public ChangeTokenProducer() { Task.Run(()=> { while (true) { Thread.Sleep(3000);//模拟耗时 //内部状态发生改变,通知外部 RaiseChanged(); } }); } public IChangeToken GetReloadToken () => _changetoken; private void RaiseChanged() { //产生新的cts var previousToken = Interlocked.Exchange(ref _changetoken, new ConfigurationReloadToken()); //触发老的cts动做 //外界执行响应动做时,经过GetReloadToken()获取新的cts,执行相应动做,并从新绑定回调函数 previousToken.OnReload(); } } }
IConfigurationSource
的实现IConfigurationSource
拥有一个实现IFileProvider
接口的类属性。默认实现为PhysicalFileProvider
类,文件监控目录默认为程序集根目录。该类提供文件的访问和监控功能。在Build
方法中实例化JsonFileConfigurationProvider
,并将自身传递进去。
在.NetCore源码中JsonConfigurationSource
是继承 抽象类FileConfigurationSource
的。此处合并了两个类的代码。
public class JsonFileConfigurationSource : IConfigurationSource { public IFileProvider FileProvider { get; set; } public IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonFileConfigurationProvider(this); } public void EnsureDefaults(IConfigurationBuilder builder) { FileProvider = FileProvider ?? builder.GetFileProvider(); } } public static class FileConfigurationExtensions { /// 获取默认的IFileProvider,root目录默认为程序集根目录(AppContext.BaseDirectory) public static IFileProvider GetFileProvider(this IConfigurationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty); } }
IConfigurationProvider
的实现在Core的源码中继承关系为JsonConfigurationProvider: FileConfigurationProvider:ConfigurationProvider:IConfigurationProvider
。本项目代码合并了JsonConfigurationProvider FileConfigurationProvider
这两个类
ConfigurationProvider
的实现该类使用一个字典用于保存配置项的字符串键值对。并拥有一个类型为 ConfigurationReloadToken
的字段。在配置文件发生更改时,_reloadToken
的状态发生改变,外部能够经过观察该字段的状态来得知配置文件发生更改。
/// <summary> /// 配置提供者抽象类 /// </summary> public abstract class ConfigurationProvider : IConfigurationProvider { private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken(); /// 初始化存储配置的字典,键值忽略大小写 protected ConfigurationProvider() { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } /// 存储配置的键值对,protected只能在子类中访问 protected IDictionary<string, string> Data { get; set; } /// 读取键值 public virtual bool TryGet(string key, out string value) => Data.TryGetValue(key, out value); /// 设置键值 public virtual void Set(string key, string value) => Data[key] = value; /// 加载配置数据源,使用virtual修饰符,在子类中实现重写 public virtual void Load() { } public IChangeToken GetReloadToken() { return _reloadToken; } /// <summary> /// 触发change token,并生成一个新的change token /// </summary> protected void OnReload() { //原子操做:赋值并返回原始值 var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()); previousToken.OnReload(); } }
JsonFileConfigurationProvider
的实现该类继承于抽象类ConfigurationProvider
。
在构造函数中监听 FileProvider.Watch()
方法返回的IChangeToken,收到更改通知时执行如下两个动做,一个是从新读取文件流,加载到字典中;另外一个是改变_reloadToken
的状态,用于通知外部:已经从新加载配置文件。因为在本项目中直接引用了MS的PhysicalFileProvider
,而该类监听文件返回的是微软的IChangeToken
。为了兼容项目代码,经过一个适配类来转换接口。
public class JsonFileConfigurationProvider : ConfigurationProvider, IDisposable { private readonly IDisposable _changeTokenRegistration; public JsonFileConfigurationSource Source { get; } public JsonFileConfigurationProvider(JsonFileConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Source = source; if (Source.ReloadOnChange && Source.FileProvider != null) { //1.IFileProvider.Watch(string filter) 返回IChangeToken //2.绑定IChangeToken的回调函数(1。生成新的IChangeToken 2.读取配置文件、向ConfigurationRoot传递消息) //3.检测到文件更改时,触发回调 //4.为新的IChangeToken绑定回调函数 _changeTokenRegistration = ChangeToken.OnChange( () => new IChangeTokenAdapter(Source.FileProvider.Watch(Source.Path)), () => { Thread.Sleep(Source.ReloadDelay); Load(reload: true); }); } } //从新加载文件并向IConfigurationRoot传递更改通知 private void Load(bool reload) { var file = Source.FileProvider?.GetFileInfo(Source.Path); if (file == null || !file.Exists) { if (Source.Optional || reload) // Always optional on reload { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } else { //处理异常 } } else { // Always create new Data on reload to drop old keys if (reload) { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } using (var stream = file.CreateReadStream()) { try { Load(stream); } catch (Exception e) { HandleException(new FileLoadExceptionContext() { Exception = e, Provider = this, Ignore = true }); } } } //触发IConfigurationProvider._cts.Cancel(),向IConfigurationRoot传递更改通知 OnReload(); } public override void Load() { Load(reload: false); } /// 从文件流中加载数据到IConfigurationProvider的Data中 public void Load(Stream stream) { try { //.NetCore3.0使用JsonDocument读取json文件,生成结构化文档: /// [key] 节点1-1:节点2-1:节点3-1 [value] Value1 /// [key] 节点1-1:节点2-2:节点3-2 [value] Value2 /// Data = JsonConfigurationFileParser.Parse(stream); //此处使用Newtonsoft.Json,简单的序列化为普通键值对 using (StreamReader sr = new StreamReader(stream)) { String jsonStr = sr.ReadToEnd(); Data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonStr); } } catch (Exception e) { throw new FormatException("读取文件流失败", e); } } public void Dispose() => Dispose(true); /// 释放_changeTokenRegistration protected virtual void Dispose(bool disposing) { _changeTokenRegistration?.Dispose(); } } /// 适配器类 /// 将Microsoft.Extensions.Primitives.IChangeToken转换为CoreWebApp.Primitives.IChangeToken public class IChangeTokenAdapter : IChangeToken { public IChangeTokenAdapter(IChangeTokenMS msToken) { MsToken = msToken ?? throw new ArgumentNullException(nameof(msToken)); } private IChangeTokenMS MsToken { get; set; } public bool HasChanged => MsToken.HasChanged; public bool ActiveChangeCallbacks => MsToken.ActiveChangeCallbacks; public IDisposable RegisterChangeCallback(Action<object> callback, object state) { return MsToken.RegisterChangeCallback(callback, state); } }
ConfigurationSection
类的实现{ "OptionV1": { "OptionV21": "ValueV21", "OptionV22": { "OptionV31": "ValueV31", "OptionV32": "ValueV32" } } }
对于如上的配置文件会保存为key "OptionV1:OptionV22:OptionV31" value "ValueV31"
的格式,这样同时将节点间的层级关系也保存了下来。经过ConfigurationRoot
访问键值须要提供键的全路径。ConfigurationSection
类至关于定位了某个节点,经过ConfigurationSection
访问键值只须要经过相对路径。
到此为止,ConfigurationRoot
已经构建完成,而后经过DI模块以单例模式注入到系统中。在控制器中能够经过IConfiguration
访问到全部配置源的键值对,而且当配置文件发生改变时从新加载IConfigurationProvider
。下篇文章将会讲述从如何经过强类型IOptions
访问配置项。