浅析 .Net Core中Json配置的自动更新

Pre

很早在看 Jesse 的Asp.net Core快速入门的课程的时候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自动重载配置的,作为一名有着严重"造轮子"情节的程序员,最近在折腾一个博客系统,也想造出一个这样能自动更新以Mysql为数据源的ConfigureSource,于是点开了AddJsonFile这个拓展函数的源码,发现别有洞天,蛮有意思,本篇文章就简单地聊一聊Json config的ReloadOnChange是如何实现的,在学习ReloadOnChange的过程中,我们会把Configuration也顺带撩一把??,希望对小伙伴们有所帮助.

 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration(option =>
                    {
                        option.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true);
                    })
                .UseStartup<Startup>();

在Asp .net core中如果配置了json数据源,把reloadOnChange属性设置为true即可实现当文件变更时自动更新配置,这篇博客我们首先从它的源码简单看一下,看完你可能还是会有点懵的,别慌,我会对这些代码进行精简,做个简单的小例子,希望能对你有所帮助.

一窥源码

AddJson

首先,我们当然是从这个我们耳熟能详的扩展函数开始,它经历的演变过程如下.

    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,string path,bool optional,bool reloadOnChange)
    {
      return builder.AddJsonFile((IFileProvider) null, path, optional, reloadOnChange);
    }

传递一个null的FileProvider给另外一个重载Addjson函数.
敲黑板,Null的FileProvider很重要,后面要考??.

    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,IFileProvider provider,string path,bool optional,bool reloadOnChange)
    {
      return builder.AddJsonFile((Action<JsonConfigurationSource>) (s =>
      {
        s.FileProvider = provider;
        s.Path = path;
        s.Optional = optional;
        s.ReloadOnChange = reloadOnChange;
        s.ResolveFileProvider();
      }));
    }

把传入的参数演变成一个Action委托给JsonConfigurationSource的属性赋值.

    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
    {
      return builder.Add<JsonConfigurationSource>(configureSource);
    }

最终调用的builder.add(action)方法.

    public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder,Action<TSource> configureSource)where TSource : IConfigurationSource, new()
    {
      TSource source = new TSource();
      if (configureSource != null)
        configureSource(source);
      return builder.Add((IConfigurationSource) source);
    }

在Add方法里,创建了一个Source实例,也就是JsonConfigurationSource实例,然后把这个实例传为刚刚的委托,这样一来,我们在最外面传入的"appsettings.json",optional:true,reloadOnChange:true参数就作用到这个示例上了.
最终,这个实例添加到builder中.那么builder又是什么?它能干什么?

ConfigurationBuild

前面提及的builder默认情况下是ConfigurationBuilder,我对它的进行了简化,关键代码如下.

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

        public IConfigurationBuilder Add(IConfigurationSource source)
        {
            Sources.Add(source);
            return this;
        }

        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);
        }
    }

可以看到,这个builder中有个集合类型的Sources,这个Sources可以保存任何实现了IConfigurationSource的Source,前面聊到的JsonConfigurationSource就是实现了这个接口,常用的还有MemoryConfigurationSource,XmlConfigureSource,CommandLineConfigurationSource等.

另外,它有一个很重要的build方法,这个build方法在WebHostBuilder方法执行build的时候也被调用,不要问我WebHostBuilder.builder方法什么执行的??.

public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

在ConfigureBuilder的方法里面就调用了每个Source的Builder方法,我们刚刚传入的是一个JsonConfigurationSource,所以我们有必要看看JsonSource的builder做了什么.
这里是不是被这些builder绕哭了? 别慌,下一篇文章中我会讲解如何自定义一个ConfigureSoure,会把Congigure系列类UML类图整理一下,应该会清晰很多.

JsonConfigurationSource

    public class JsonConfigurationSource : FileConfigurationSource
    {
        public override IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            EnsureDefaults(builder);
            return new JsonConfigurationProvider(this);
        }
    }

这就是JsonConfigurationSource的所有代码,未精简,它只实现了一个Build方法,在Build内,EnsureDefaults被调用,可别小看它,之前那个空的FileProvider在这里被赋值了.

        public void EnsureDefaults(IConfigurationBuilder builder)
        {
            FileProvider = FileProvider ?? builder.GetFileProvider();
        }
        public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
        {
            return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
        }

可以看到这个FileProvider默认情况下就是PhysicalFileProvider,为什么对这个FileProvider如此宠幸让我花如此大的伏笔要强调它呢?往下看.

JsonConfigurationProvider && FileConfigurationProvider

在JsonConfigurationSource的build方法内,返回的是一个JsonConfigurationProvider实例,所以直觉告诉我,在它的构造函数内必有猫腻??.

    public class JsonConfigurationProvider : FileConfigurationProvider
    {

        public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }

        public override void Load(Stream stream)
        {
            try {
                Data = JsonConfigurationFileParser.Parse(stream);
            } catch (JsonReaderException e)
            {
                throw new FormatException(Resources.Error_JSONParseError, e);
            }
        }
    }

看不出什么的代码,事出反常必有妖~~
看看base的构造函数.

        public FileConfigurationProvider(FileConfigurationSource source)
        {
            Source = source;

            if (Source.ReloadOnChange && Source.FileProvider != null)
            {
                _changeTokenRegistration = ChangeToken.OnChange(
                    () => Source.FileProvider.Watch(Source.Path),
                    () => {
                        Thread.Sleep(Source.ReloadDelay);
                        Load(reload: true);
                    });
            }
        }

真是个天才,问题就在这个构造函数里,它构造函数调用了一个ChangeToken.OnChange方法,这是实现ReloadOnChange的关键,如果你点到这里还没有关掉,恭喜,好戏开始了.

ReloadOnChange

Talk is cheap. Show me the code (屁话少说,放过来).

    public static class ChangeToken
    {
        public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
        {
            return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
        }
    }

OnChange方法里,先不管什么func,action,就看看这两个参数的名称,producer,consumer,生产者,消费者,不知道看到这个关键词想到的是什么,反正我想到的是小学时学习食物链时的??与??.

那么我们来看看这里的??是什么,??又是什么,还得回到FileConfigurationProvider的构造函数.

可以看到生产者??是:

() => Source.FileProvider.Watch(Source.Path)

消费者??是:


() => {
    Thread.Sleep(Source.ReloadDelay);
    Load(reload: true);
}

我们想一下,一旦有一条??跑出来,就立马被??吃了,

那我们这里也一样,一旦有FileProvider.Watch返回了什么东西,就会发生Load()事件来重新加载数据.

??与??好理解,可是代码就没那么好理解了,我们通过OnChange的第一个参数Func<IChangeToken> changeTokenProducer方法知道,这里的??,其实是IChangeToken.

IChangeToken

    public interface IChangeToken
    {
        bool HasChanged { get; }

        bool ActiveChangeCallbacks { get; }

        IDisposable RegisterChangeCallback(Action<object> callback, object state);
    }

IChangeToken的重点在于里面有个RegisterChangeCallback方法,??吃??的这件事,就发生在这回调方法里面.
我们来做个??吃??的实验.

实验1

 static void Main()
        {
            //定义一个C:\Users\liuzh\MyBox\TestSpace目录的FileProvider
            var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace");

            //让这个Provider开始监听这个目录下的所有文件
            var changeToken = phyFileProvider.Watch("*.*");

            //注册??吃??这件事到回调函数
            changeToken.RegisterChangeCallback(_=> { Console.WriteLine("老鼠被蛇吃"); }, new object());

            //添加一个文件到目录
            AddFileToPath();

            Console.ReadKey();

        }

        static void AddFileToPath()
        {
            Console.WriteLine("老鼠出洞了");
            File.Create("C:\\Users\\liuzh\\MyBox\\TestSpace\\老鼠出洞了.txt").Dispose();
        }

这是运行结果

可以看到,一旦在监听的目录下创建文件,立即触发了执行回调函数,但是如果我们继续手动地更改(复制)监听目录中的文件,回调函数就不再执行了.

这是因为changeToken监听到文件变更并触发回调函数后,这个changeToken的使命也就完成了,要想保持一直监听,那么我们就在在回调函数中重新获取token,并给新的token的回调函数注册通用的事件,这样就能保持一直监听下去了.
这也就是ChangeToken.Onchange所作的事情,我们看一下源码.

   public static class ChangeToken
    {
        public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
        {
            return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
        }
    }
    public class ChangeTokenRegistration<TAction>
    {
        private readonly Func<IChangeToken> _changeTokenProducer;
        private readonly Action<TAction> _changeTokenConsumer;
        private readonly TAction _state;

        public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TAction> changeTokenConsumer, TAction state)
        {
            _changeTokenProducer = changeTokenProducer;
            _changeTokenConsumer = changeTokenConsumer;
            _state = state;

            var token = changeTokenProducer();

            RegisterChangeTokenCallback(token);
        }

        private void RegisterChangeTokenCallback(IChangeToken token)
        {
            token.RegisterChangeCallback(_ => OnChangeTokenFired(), this);
        }

        private void OnChangeTokenFired()
        {
            var token = _changeTokenProducer();

            try
            {
                _changeTokenConsumer(_state);
            }
            finally
            {
                // We always want to ensure the callback is registered
                RegisterChangeTokenCallback(token);
            }
        }
    }

简单来说,就是给token注册了一个OnChangeTokenFired的回调函数,仔细看看OnChangeTokenFired里做了什么,总体来说三步.

  1. 获取一个新的token.
  2. 调用消费者进行消费.
  3. 给新获取的token再次注册一个OnChangeTokenFired的回调函数.

如此周而复始~~

实验2

既然知道了OnChange的工作方式,那么我们把实验1的代码修改一下.

        static void Main()
        {
            var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace");
            ChangeToken.OnChange(() => phyFileProvider.Watch("*.*"),
                () => { Console.WriteLine("老鼠被蛇吃"); });
            Console.ReadKey();
        }

执行效果看一下

可以看到,只要被监控的目录发生了文件变化,不管是新建文件,还是修改了文件内的内容,都会触发回调函数,其实JsonConfig中,这个回调函数就是Load(),它负责重新加载数据,可也就是为什么Asp .net core中如果把ReloadOnchang设置为true后,Json的配置一旦更新,配置就会自动重载.

PhysicalFilesWatcher

那么,为什么文件一旦变化,就会触发ChangeToken的回调函数呢? 其实PhysicalFileProvider中调用了PhysicalFilesWatcher对文件系统进行监视,观察PhysicalFilesWatcher的构造函数,可以看到PhysicalFilesWatcher需要传入FileSystemWatcher,FileSystemWatchersystem.io下的底层IO类,在构造函数中给这个Watcher的Created,Changed,Renamed,Deleted注册EventHandler事件,最终,在这些EventHandler中会调用ChangToken的回调函数,所以文件系统一旦发生变更就会触发回调函数.

    public PhysicalFilesWatcher(string root,FileSystemWatcher fileSystemWatcher,bool pollForChanges,ExclusionFilters filters)
    {
      this._root = root;
      this._fileWatcher = fileSystemWatcher;
      this._fileWatcher.IncludeSubdirectories = true;
      this._fileWatcher.Created += new FileSystemEventHandler(this.OnChanged);
      this._fileWatcher.Changed += new FileSystemEventHandler(this.OnChanged);
      this._fileWatcher.Renamed += new RenamedEventHandler(this.OnRenamed);
      this._fileWatcher.Deleted += new FileSystemEventHandler(this.OnChanged);
      this._fileWatcher.Error += new ErrorEventHandler(this.OnError);
      this.PollForChanges = pollForChanges;
      this._filters = filters;
      this.PollingChangeTokens = new ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>();
      this._timerFactory = (Func<Timer>) (() => NonCapturingTimer.Create(new TimerCallback(PhysicalFilesWatcher.RaiseChangeEvents), (object) this.PollingChangeTokens, TimeSpan.Zero, PhysicalFilesWatcher.DefaultPollingInterval));
    }

蒋金楠老师有一篇优秀的文章介绍FileProvider,有兴趣的可以看一下
https://www.cnblogs.com/artech/p/net-core-file-provider-02.html.

如果你和我一样,对源码感兴趣,可以从官方的aspnet/Extensions中下载源码研究:https://github.com/aspnet/Extensions

在下一篇文章中,我会讲解如何自定义一个以Mysql为数据源的ConfigureSoure,并实现自动更新功能,同时还会整理Configure相关类的UML类图,有兴趣的可以关注我以便第一时间收到下篇文章.
本文章涉及的代码地址:https://github.com/liuzhenyulive/MiniConfiguration

原文地址:https://www.cnblogs.com/CoderAyu/p/10776845.html

时间: 2024-07-29 20:51:19

浅析 .Net Core中Json配置的自动更新的相关文章

ASP.NET Core 中的配置

前言 配置在我们开发过程中必不可少,ASP.NET中的配置在 Web.config 中.也可配置在如:JSON.XML.数据库等(但ASP.NET并没提供相应的模块和方法). 在ASP.NET Core中Web.config已经不存在了(但如果托管到 IIS 的时候可以使用 web.config 配置 IIS), 而是用appsettings.json和appsettings.(Development.Staging.Production).json配置文件 (可以理解为ASP.NET中的Web

大话DI依赖注入+IOC控制反转(二) 之 浅析.Net Core中的DI与IOC

原文:大话DI依赖注入+IOC控制反转(二) 之 浅析.Net Core中的DI与IOC   转发时请注明原创作者及地址,否则追究责任.原创:alunchen 在上一篇文章中,我们聊了很多关于定义的方面,比较孤燥,下面我们结合.Net Core聊一下依赖注入&控制反转. 三种对象生命周期 关于.Net Core中的容器,有三种对象的生命周期,这个从网上搜索也有大堆的资料.为了循序渐进,我们这里介绍一下. Transient 称为短暂,意思是需要使用时就创建一个新的对象.从容易层面讲,当从容器取出

linux系统初始化--&#8203;配置ntp自动更新时间

配置ntp自动更新时间 安装ntpdate程序 Shell># yum –y install ntpdate 使用crontab将系统设定为每天0:00与NTP服务器同步时间 Shell># crontab –e 0   0  *  *  *  ntpdate   your_ntp_server_domain_name   or  your_ntp_server_ip_addr 保存退出, 为了我们步骤继续下去,我们执行一次同步 Shell># ntpdate ntp.jn.idc 如果

Asp.Net Core中Json序列化处理整理

一.Asp.Net Core中的Json序列化处理使用的是Newtonsoft.Json,更多参考:C# Newtonsoft.Json JsonSerializerSettings配置序列化操作,C# Json序列化工具--Newtonsoft.Json简介和使用 1.Newtonsoft.Json仅 依赖.Net Standard所以支持.Net Framework也支持.Net Core 2.更多说明 /* * 1.在Core Mvc中JsonResult 默认支持Get请求 * 2.使用

在Centos6.6中如何配置软件下载更新源地址

一. yum是什么yum是(Yellow dog Updater, Modified)主要功能是更方便的添加/删除/更新RPM包.它能自动解决包的依赖性问题.它能便于管理大量系统的更新问题 二. yum特点1)可以同时配置多个资源库(Repository)2)简洁的配置文件(/etc/yum.conf)3)自动解决增加或删除rpm包时遇到的倚赖性问题4)使用方便5)保持与RPM数据库的一致性 三. yum安装[[email protected] ~]# yum -y install yum-3.

SQL触发器:根据客户档案中的地区信息自动更新联系页中的省份和城市

USE [UFDATA_001_2017] GO   /****** Object:  Trigger [dbo].[JDS_Customer_UPDATECITY]    Script Date: 07/06/2018 16:53:04 ******/ SET ANSI_NULLS ON GO   SET QUOTED_IDENTIFIER ON GO     CREATE TRIGGER [dbo].[JDS_Customer_UPDATECITY] ON [dbo].[Customer]

在Asp.Net Core中关于appsettings.json的快速简便的读取和设置方式

在Asp.Net Core 中,配置信息已从原来Asp.Net的XML格式改为了更为流行的JSON格式,配置文件也由原来的App.config改成了appsettings.json. 那么对于这个appsettings.json中的配置信息的读取,使用最多的是使用与配置对应的实体模型,调用services.Configure<TOptions>()泛型方法载入配置. 这种方式的好处在于,将配置数据载入到对应的实体中后,项目的其它地方都可以使用,常见的是用于Controller中. 其缺点是不快

.Net core下的配置设置(一)——Configuration

ASP.NET Core 中提供了一个Configuration 包,用以应用配置基于配置提供程序建立的键值对.这里以json文件配置的方式,简单的介绍一下它的用法. 首先定义一个配置文件appsettings.json: {    "key1": "value1",    "key2": -1,        "subsection":  {            "key1": "value2

带你入门SpringCloud 之 通过SpringCloud Bus 自动更新配置

前言 在<带你入门SpringCloud统一配置 | SpringCloud Config>中通过 SpringCloud Config 完成了统一配置基础环境搭建,但是并没有实现配置修改自动更新的操作(GitHub 或Gitee 修改配置后,需要重启配置服务才能更新配置). 本文是<带你入门SpringCloud统一配置 | SpringCloud Config>的续篇,通过 SpringCloud Bus 完成配置修改自动更新的操作介绍. 阅读本文前需要你先移步<带你入门