• 重新整理 .net core 实践篇—————服务的配置更新[十三]


    前言

    前文讲述了,服务和配置直接的配合,这一节写一下,当配置文件修改了,每个服务如何感知自己的配置。

    正文

    服务感知到自己的配置发生变化,这就牵扯出两个东西:

    IoptionsMonitor<out TOptions>
    
    IoptionSnapshot<out TOptions>
    

    在作用域范围使用IoptionSnapshot,在单例中使用IoptionsMonitor 。

    IoptionsMonitor

    先来演示作用域范围的使用。
    配置:

    {
      "SelfService": {
        "name" : "zhangsan" 
      }
    }
    

    SelfServiceOption:

    public class SelfServiceOption
    {
    	public string Name { get; set; }
    }
    

    服务:

    public class SelfService : ISelfService
    {
    	IOptionsSnapshot<SelfServiceOption> _options;
    	public SelfService(IOptionsSnapshot<SelfServiceOption> options)
    	{
    		this._options = options;
    	}
    	public string ShowOptionName()
    	{
    		return _options.Value.Name;
    	}
    }
    

    注册:

    services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
    {
    	BinderOptions.BindNonPublicProperties = true;
    });
    services.AddScoped<ISelfService, SelfService>();
    

    测试:

    [HttpGet]
    public int GetService([FromServices]ISelfService selfService)
    {
    	Console.WriteLine(selfService.ShowOptionName());
    	return 1;
    }
    

    结果:

    第一次访问后修改为zhangsan_change,再次访问接口,会呈现上述效果。

    那么为什么使用IoptionsMonitor,而为什么Ioptions 没有用呢。

    前一篇写过Ioptions 的实现类OptionsManager,这个是有缓存的_cache,如下:

    public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
    {
    	private readonly IOptionsFactory<TOptions> _factory;
    	private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache
    
    	/// <summary>
    	/// Initializes a new instance with the specified options configurations.
    	/// </summary>
    	/// <param name="factory">The factory to use to create options.</param>
    	public OptionsManager(IOptionsFactory<TOptions> factory)
    	{
    		_factory = factory;
    	}
    
    	/// <summary>
    	/// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
    	/// </summary>
    	public TOptions Value
    	{
    		get
    		{
    			return Get(Options.DefaultName);
    		}
    	}
    
    	/// <summary>
    	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
    	/// </summary>
    	public virtual TOptions Get(string name)
    	{
    		name = name ?? Options.DefaultName;
    
    		// Store the options in our instance cache
    		return _cache.GetOrAdd(name, () => _factory.Create(name));
    	}
    }
    

    IoptionsMonitor的实现类也是OptionsManager,但是人家是作用域模式。

    在Addoptions中:

    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
    

    也就是说每创建一个SelfService,就会创建一个OptionsManager。缓存自然只在作用域内有效。

    好的,那么来看下单例。

    IoptionsMonitor

    服务:

    public class SelfService : ISelfService
    {
    	IOptionsMonitor<SelfServiceOption> _options;
    	public SelfService(IOptionsMonitor<SelfServiceOption> options)
    	{
    		this._options = options;
    
    		_options.OnChange((selftServiceOptions) =>
    		{
    			Console.WriteLine("alter change:"+selftServiceOptions.Name);
    		});
    	}
    	public string ShowOptionName()
    	{
    		return _options.CurrentValue.Name;
    	}
    }
    

    注册:

    services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
    {
    	BinderOptions.BindNonPublicProperties = true;
    });
    services.AddSingleton<ISelfService, SelfService>();
    

    测试接口:

    [HttpGet]
    public int GetService([FromServices]ISelfService selfService)
    {
    	Console.WriteLine(selfService.ShowOptionName());
    	return 1;
    }
    

    同意是修改钱访问一次,修改后访问一次。

    结果如下:

    那么看下IOptionMonitor的实现类OptionsMonitor:

    public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable where TOptions : class, new()
    {
    	private readonly IOptionsMonitorCache<TOptions> _cache;
    	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;
    
    	/// <summary>
    	/// Constructor.
    	/// </summary>
    	/// <param name="factory">The factory to use to create options.</param>
    	/// <param name="sources">The sources used to listen for changes to the options instance.</param>
    	/// <param name="cache">The cache used to store options.</param>
    	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);
    		}
    	}
    
    	private void InvokeChanged(string name)
    	{
    		name = name ?? Options.DefaultName;
    		_cache.TryRemove(name);
    		var options = Get(name);
    		if (_onChange != null)
    		{
    			_onChange.Invoke(options, name);
    		}
    	}
    
    	/// <summary>
    	/// The present value of the options.
    	/// </summary>
    	public TOptions CurrentValue
    	{
    		get => Get(Options.DefaultName);
    	}
    
    	/// <summary>
    	/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
    	/// </summary>
    	public virtual TOptions Get(string name)
    	{
    		name = name ?? Options.DefaultName;
    		return _cache.GetOrAdd(name, () => _factory.Create(name));
    	}
    
    	/// <summary>
    	/// Registers a listener to be called whenever <typeparamref name="TOptions"/> changes.
    	/// </summary>
    	/// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param>
    	/// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns>
    	public IDisposable OnChange(Action<TOptions, string> listener)
    	{
    		var disposable = new ChangeTrackerDisposable(this, listener);
    		_onChange += disposable.OnChange;
    		return disposable;
    	}
    
    	/// <summary>
    	/// Removes all change registration subscriptions.
    	/// </summary>
    	public void Dispose()
    	{
    		// Remove all subscriptions to the change tokens
    		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);
    
    		public void Dispose() => _monitor._onChange -= OnChange;
    	}
    }
    

    给每个给做了监听哈:

    foreach (var source in _sources)
    {
    	var registration = ChangeToken.OnChange(
    		  () => source.GetChangeToken(),
    		  (name) => InvokeChanged(name),
    		  source.Name);
    
    	_registrations.Add(registration);
    }
    

    这个IOptionsChangeTokenSource怎么来的呢?是在我们的configure配置方法中:

    public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
    	where TOptions : class
    {
    	if (services == null)
    	{
    		throw new ArgumentNullException(nameof(services));
    	}
    
    	if (config == null)
    	{
    		throw new ArgumentNullException(nameof(config));
    	}
    
    	services.AddOptions();
    	services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
    	return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
    }
    

    看到这一段:services.AddSingleton<IOptionsChangeTokenSource>(new ConfigurationChangeTokenSource(name, config));。

    当有修改后,那么会调用:

    private void InvokeChanged(string name)
    {
    	name = name ?? Options.DefaultName;
    	_cache.TryRemove(name);
    	var options = Get(name);
    	if (_onChange != null)
    	{
    		_onChange.Invoke(options, name);
    	}
    }
    

    这里面会移除缓存_cache.TryRemove(name);,然后重新新调用: Get(name);也就会再绑定一次。

    这里面有一个值得注意的是,如果有回调,不一定是本身这个服务的配置修改,可能是其他服务的配置修改了,也会被通知,因为这个是文件发生变化就会被通知。

    原理如下:

    GetSession会返回一个 ConfigurationSection。那么它里面的GetReloadToken是这样的:

     public IChangeToken GetReloadToken() => _root.GetReloadToken();
    

    这返回了ConfigurationRoot的GetReloadToken。

    实验一下:

    {
        "SelfService": {
        "name": "zhangsan"
      },
      "SelfService2": {
        "name" : "good one" 
      }
    }
    

    改成:

    {
        "SelfService": {
        "name": "zhangsan"
      },
      "SelfService2": {
        "name" : "good one1" 
      }
    }
    

    结果:

    索引我们可以在服务里面配置增加一个version版本号,如果版本修改了,然后才做相应的操作。

    以上只是个人整理,如有错误,望请指点。

    下一节:配置验证。

  • 相关阅读:
    语言相关
    一道简单DP题
    一道概率题
    Android CrashHandler
    一道简单数学题
    面试中遇到的随机题目
    VMWare 无损扩展磁盘大小
    Android 源码编译记录
    Android handler 内存泄露分析及解决方法
    Android 反编译
  • 原文地址:https://www.cnblogs.com/aoximin/p/14852581.html
Copyright © 2020-2023  润新知