.NetCore读取配置IOptions<T>、IOptionsMonitor<T>、IOptionsSnapshot<T>

.NetCore配置热更新

问题

.netcore 读取配置支持热更新,默认CreateDefaultBuilder中读取配置时也设定了开启热更新,然而在项目中发现更改了配置后使用的还是旧的配置信息,经过查看官方文档发现IOptions<>不会读取在应用启动后对 JSON 配置文件所做的更改,刚好项目中使用的是IOptions<>去读取JSON配置,需要使用IOptionsSnapshot<>才可以,下面来探究下IOptions<>IOptionsMonitor<>IOptionsSnapshot<> 有什么不同:

例子

构建一个webapi的项目(此处使用.net5),在控制器中注入三种Options接口,在方法中分别输出值代码如下:

        public TestController(ILogger<TestController> logger,IOptions<User> options,IOptionsMonitor<User> optionsMonitor,IOptionsSnapshot<User> optionsSnapshot)
        {
            _logger = logger;
            _options = options;
            _optionsMonitor = optionsMonitor;
            _optionsSnapshot = optionsSnapshot;
        }
                [HttpGet]
        public async Task<IActionResult> GetAsync()
        {
            _logger.LogInformation($"options修改前:{_options.Value.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsMonitor修改前:{_optionsMonitor.CurrentValue.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsSnapshot修改前:{_optionsSnapshot.Value.Name}");
            _logger.LogInformation("--------------------");
            await Task.Delay(TimeSpan.FromSeconds(30));
            _logger.LogInformation("修改配置文件");
            _logger.LogInformation($"options修改后:{_options.Value.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsMonitor修改后:{_optionsMonitor.CurrentValue.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsSnapshot修改后:{_optionsSnapshot.Value.Name}");
            _logger.LogInformation("--------------------");
            return Ok("ok");
        }

初始JSON的配置:

    "Info": {
        "Name": "dong35888"
    },

发布代码使用IIS部署代码之后运行,查看运行日志
日志
第一次请求方法:刚进入方法输出的值都一样,在方法进入休眠时手动修改JSON中name的值,休眠时间到方法继续运行,发现optionsMonitor输出的值修改了,options和optionsSnapshot还是原有的值;第二次请求方法,options输出的还是旧值,optionsMonitor和optionsSnapshot输出的都是修改之后的值,方法进入休眠再次修改JSON中name的值,方法继续options输出最开始的值没变,optionsMonitor输出的最新的值即第二次修改的值,optionsSnapshot输出没变(输出第一次修改后的值);

  • IOptions<> 获取的值一直不变
  • IOptionsMonitor<> 一直获取的是最新的值
  • IOptionsSnapshot<> 一次请求中的值不变,如果有变更,再一次请求中可以获取到新的值

源码

借助源码来分析分析:

        public static IServiceCollection AddOptions(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
            services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
            services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
            return services;
        }
    public interface IOptionsSnapshot<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] out TOptions> :
        IOptions<TOptions>
        where TOptions : class
    {
        /// <summary>
        /// Returns a configured <typeparamref name="TOptions"/> instance with the given name.
        /// </summary>
        TOptions Get(string name);
    }

可以看到IOption是以单例方式注册到DI,IOptionsSnapshot是scope方式注册到DI,并且IOptionsSnapshot继承了IOption;单例只构建一次,那就是项目启动IOption构建之后就保持不变了,IOptionsSnapshot每一次请求构建一次,所以在单个方法中它保持不变,第二次请求重新构建时获取了新的值;
针对IOptionsMonitor我们在做一个事例:

        private readonly ILogger<TestController> _logger;
        private readonly IOptionsMonitor<User> _optionsMonitor;
        private readonly User _optionsMonitorUser;


        public TestController(ILogger<TestController> logger,IOptions<User> options,IOptionsMonitor<User> optionsMonitor,IOptionsSnapshot<User> optionsSnapshot)
        {
            _logger = logger;
            _optionsMonitor = optionsMonitor;
            _optionsMonitorUser = optionsMonitor.CurrentValue;
        }

        [HttpGet("Monitor")]
        public async Task<IActionResult> MonitorAsync()
        {
            _logger.LogInformation($"optionsMonitor修改前:{_optionsMonitor.CurrentValue.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsMonitorUser修改前:{_optionsMonitorUser.Name}");
            _logger.LogInformation("--------------------");
            await Task.Delay(TimeSpan.FromSeconds(30));
            _logger.LogInformation("手动修改配置文件");
            _logger.LogInformation($"optionsMonitor修改后:{_optionsMonitor.CurrentValue.Name}");
            _logger.LogInformation("--------------------");
            _logger.LogInformation($"optionsMonitorUser修改后:{_optionsMonitorUser.Name}");
            _logger.LogInformation("--------------------");
            return Ok("ok");
        }

结果
在源码中看到IOptionsMonitor也是以单例注册到DI中,但它的实现类却不同,是OptionsMonitor

        public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
        {
            _factory = factory;
            _sources = sources;
            _cache = cache;

            foreach (IOptionsChangeTokenSource<TOptions> source in _sources)
            {
                IDisposable 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);
            TOptions options = Get(name);
            if (_onChange != null)
            {
                _onChange.Invoke(options, name);
            }
        }

通过源码大概看到只要监听到变更,就重新获取,从上一个示例也证明这点,_optionsMonitorUser是在构造函数中从_optionsMonitor属性获取到的,修改前与_optionsMonitor中拿的值一致,休眠修改后,_optionsMonitorUser还是原值,_optionsMonitor里面已经是新的对象了;

结论

  • IOption<> 是单例,一旦生成就不会再更改,除非程序停止销毁后再构建,那些确保不会发生变更的可以使用,或者修改之后重新启动程序;
  • OptionsMonitor<> 也是单例,但是它只要配置有变更,它就会更新,这样可能造成一方法中前面和后面引用的值不一致,不过可以先通过CurrentValue属性拿到值在使用;
  • IOptionsSnapshot<> 是scope注册,一次请求里值不会变,变更之后再下一次请求能获取到最新的值,刚好符合我的项目要求;
    以上结论纯属个人总结,如有不正确出请指正。

资料

Microsoft.Extensions.Options.ConfigurationExtensions github地址
microsoft文档

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值