zoukankan      html  css  js  c++  java
  • 设计个性化、灵活、实时更新的配置管理器

    这里所说的个性化、灵活、实时更新的定义?

    个性化,是指你可以随意定义自己想要的配置结构、保存格式、存放位置等等。

    灵活,是指可以方便的对配置进行读、写操作,并可以很容易实现任意多个配置管理器。

    实时更新,是指在配置发生改变时可以实时的更新,且不会重启Web应用程序。

    下面开始讲解设计。既然是配置管理器,那还是先定义好接口吧,请看IFileConfigManager<T>

    /// <summary>
    /// Interface containing all properties and methods to be implemented
    /// by file configuration manager.
    /// </summary>
    /// <typeparam name="T">The type of config entity.</typeparam>
    public interface IFileConfigManager<T> : IDisposable
        where T : class, new()
    {
        /// <summary>
        /// Gets the path of the config file.
        /// </summary>
        string Path { get; }
     
        /// <summary>
        /// Gets the encoding to read or write the config file.
        /// </summary>
        Encoding Encoding { get; }
     
        /// <summary>
        /// Gets the serializer of the config manager for loading or saving the config file.
        /// </summary>
        FileConfigSerializer<T> Serializer { get; }
     
        /// <summary>
        /// Gets the current config entity.
        /// </summary>
        /// <returns></returns>
        T GetConfig();
     
        /// <summary>
        /// Saves the current config entity to file.
        /// </summary>
        void SaveConfig();
     
        /// <summary>
        /// Saves a specified config entity to file.
        /// </summary>
        /// <param name="config"></param>
        void SaveConfig(T config);
     
        /// <summary>
        /// Backups the current config entity to a specified path.
        /// </summary>
        /// <param name="backupPath"></param>
        void BackupConfig(string backupPath);
     
        /// <summary>
        /// Restores config entity from a specified path and saves to the current path.
        /// </summary>
        /// <param name="restorePath"></param>
        void RestoreConfig(string restorePath);
    }

    T参数当然就是定义的配置类型了,而且必须是引用类型,有无参数构造函数。Path是配置文件的完整路径,Encoding是读取和保存配置时用的编码,Serializer是处理配置序列化和反序列化的具体实现,GetConfig()是获取当前配置,SaveConfig()是保存当前配置,SaveConfig(T config)是保存指定的配置,BackupConfig(string backupPath)备份配置到指定路径,RestoreConfig(string restorePath)从指定路径还原配置。

    接口IFileConfigManager<T>中定义的Serializer是用于支持自定义配置序列化功能的,下面看看FileConfigSerializer<T>的实现:

    public abstract class FileConfigSerializer<T>
        where T : class, new()
    {
        #region Fields
     
        // XML格式
        public static readonly FileConfigSerializer<T> Xml = new XmlFileConfigSerializer();
     
        // 二进制格式
        public static readonly FileConfigSerializer<T> Binary = new BinaryFileConfigSerializer();
     
        #endregion
     
        #region Methods
     
        // 从配置文件反序列化,使用指定的编码
        public abstract T DeserializeFromFile(string path, Encoding encoding);
     
        // 序列化到配置文件,使用指定的编码
        public abstract void SerializeToFile(T config, string path, Encoding encoding);
     
        #endregion
     
        #region XmlFileConfigSerializer
     
        // 实现默认的Xml序列化类
        private sealed class XmlFileConfigSerializer : FileConfigSerializer<T>
        {
            public override T DeserializeFromFile(string path, Encoding encoding)
            {
                return SerializationUtil.DeserializeFromXmlFile<T>(path, encoding);
            }
     
            public override void SerializeToFile(T config, string path, Encoding encoding)
            {
                SerializationUtil.SerializeToXmlFile(config, path, encoding);
            }
        }
     
        #endregion
     
        #region BinaryFileConfigSerializer
     
        // 实现默认的二进制序列化类
        private sealed class BinaryFileConfigSerializer : FileConfigSerializer<T>
        {
            public override T DeserializeFromFile(string path, Encoding encoding)
            {
                return SerializationUtil.DeserializeFromBinaryFile<T>(path, encoding);
            }
     
            public override void SerializeToFile(T config, string path, Encoding encoding)
            {
                SerializationUtil.SerializeToBinaryFile(config, path, encoding);
            }
        }
     
        #endregion
    }

     FileConfigSerializer<T>定义为抽象类,是为了方便默认的使用和扩展,里面使用的SerializationUtil类,是本人为了方便写的一个简单的序列化助手类,相信大家对对象的序列化操作不会陌生了,无非使用了System.Xml.Serialization.XmlSerializer、System.Runtime.Serialization.Formatters.Binary.BinaryFormatter、System.Runtime.Serialization.Json.DataContractJsonSerializer和System.Runtime.Serialization.NetDataContractSerializer来处理。如果不想用它们,你还可以实现FileConfigSerializer<T>进行完全的自己定义配置的加载与保存方式。对于json序列化推荐大家使用http://www.codeplex.com/json/ 。

    好了,大家已经知道了接口的定义了,下面来讲讲实时更新配置功能有哪些方法可以实现。我们知道,如果利用Web.config来配置的话,第一:如果配置内容多而杂,那会很乱;第二:如果手动修改配置,会导致Web重启(而我们并不希望它重启),所以,如果要解决上面两点问题,我们就要思考点什么了。上面我提到了Discuz!论坛的.net开源版本里配置管理,它是使用Timer来定时查检配置是否有修改,如果有修改就重新加载的,恩,这是一个可行的方案。还有其它方法吗?必须是有的,只要你肯去思考,下面列出本人想到的几个比较容易想到的方案:

    方法1:使用Timer(.net库里有三个timer,请自行选择),每隔一秒就查检一下配置文件修改时间,如果文件被修改了,正更新最后修改时间并重新加载配置内容;

    方法2:使用System.IO.FileSystemWatcher,可以实时监控配置文件,一发生改变即重新加载配置内容;

    方法3:使用System.Web.Caching.Cache,加上缓存依赖,文件更改后缓存会失效,同样可以实时重新加载配置内容。

    这三种方法中,方法3是本人比较推荐的,因为它的开销最小,而且可以实时更新配置,实现起来也是最简单的。对于新手可能看到这还不知道实现,下面再贴出本人实现上面接口的四个类,一个是默认管理器类,没有实时更新的功能,其它三个就是实现上面三种方法的管理器类了。

    internal class DefaultFileConfigManager<T> : DisposableObject, IFileConfigManager<T>
        where T : class, new()
    {
        #region Fields
     
        private string path = null;
        private Func<string> pathCreator = null;
     
        #endregion
     
        #region Constructors
     
        public DefaultFileConfigManager(Func<string> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
        {
            pathCreator.ThrowsIfNull("pathCreator");
            serializer.ThrowsIfNull("serializer");
     
            this.pathCreator = pathCreator;
            this.Encoding = encoding;
            this.Serializer = serializer;
            this.SyncRoot = new object();
            this.Config = null;
        }
     
        #endregion
     
        #region Properties
     
        public string Path
        {
            get
            {
                if (this.path == null)
                {
                    string path = this.pathCreator();
     
                    path.ThrowsIfNull("The path returned form pathCreator is null.");
     
                    this.path = path;
                    this.LazyInitialize();
                }
     
                return this.path;
            }
        }
     
        public Encoding Encoding
        {
            get;
            protected set;
        }
     
        public FileConfigSerializer<T> Serializer
        {
            get;
            protected set;
        }
     
        protected object SyncRoot
        {
            get;
            set;
        }
     
        protected virtual T Config
        {
            get;
            set;
        }
     
        #endregion
     
        #region Methods
     
        public virtual T GetConfig()
        {
            if (this.Config == null)
            {
                lock (this.SyncRoot)
                {
                    if (this.Config == null)
                    {
                        FileInfo file = new FileInfo(this.Path);
                        if (!file.Exists)
                        {
                            // make sure the existence of the config directory
                            if (!file.Directory.Exists)
                            {
                                file.Directory.Create();
                            }
                            // save the default config to file
                            this.Config = new T();
                            this.Serializer.SerializeToFile(this.Config, this.Path, this.Encoding);
                        }
                        else
                        {
                            // else, loads from the specified path
                            this.Config = this.Serializer.DeserializeFromFile(this.Path, this.Encoding);
                        }
                    }
                }
            }
     
            return this.Config;
        }
     
        public void SaveConfig()
        {
            this.SaveConfig(this.GetConfig());
        }
     
        public virtual void SaveConfig(T config)
        {
            config.ThrowsIfNull("config");
     
            lock (this.SyncRoot)
            {
                FileInfo file = new FileInfo(this.Path);
     
                // make sure the existence of the config directory
                if (!file.Directory.Exists)
                {
                    file.Directory.Create();
                }
     
                this.Config = config;
                this.Serializer.SerializeToFile(this.Config, this.Path, this.Encoding);
            }
        }
     
        public void BackupConfig(string backupPath)
        {
            backupPath.ThrowsIfNull("backupPath");
     
            T config = this.GetConfig();
            this.Serializer.SerializeToFile(config, backupPath, this.Encoding);
        }
     
        public void RestoreConfig(string restorePath)
        {
            restorePath.ThrowsIfNull("restorePath");
     
            T config = this.Serializer.DeserializeFromFile(restorePath, this.Encoding);
            this.SaveConfig(config);
        }
     
        // this method is provided to subclasses to initialize their data
        protected virtual void LazyInitialize()
        {
        }
     
        #endregion
    }
    internal sealed class FileConfigManagerWithTimer<T> : DefaultFileConfigManager<T>
        where T : class, new()
    {
        private Timer timer = null;
        private DateTime lastWriteTime = DateTime.MinValue; // a flag to notify us of the change config
     
        public FileConfigManagerWithTimer(Func<string> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
            : base(pathCreator, serializer, encoding)
        {
        }
     
        protected override void LazyInitialize()
        {
            base.LazyInitialize();
     
            // initializes the timer, with it's interval of 1000 milliseconds
            this.timer = new Timer(1000);
            this.timer.Enabled = true;
            this.timer.AutoReset = true;
            this.timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);
            this.timer.Start();
        }
     
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // disposes the timer
                this.timer.Dispose();
                this.timer = null;
            }
        }
     
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (!File.Exists(this.Path))
            {
                // the file has been deleted
                return;
            }
     
            var tempWriteTime = File.GetLastWriteTime(this.Path);
     
            // if equals to the initial value, update it and return
            if (this.lastWriteTime == DateTime.MinValue)
            {
                this.lastWriteTime = tempWriteTime;
                return;
            }
     
            // if no equals to new write time, update it and reload config
            if (this.lastWriteTime != tempWriteTime)
            {
                this.lastWriteTime = tempWriteTime;
     
                lock (this.SyncRoot)
                {
                    this.Config = this.Serializer.DeserializeFromFile(this.Path, this.Encoding);
                }
            }
        }
    }
    internal sealed class FileConfigManagerWithFileWatcher<T> : DefaultFileConfigManager<T>
        where T : class, new()
    {
        private FileWatcher watcher = null;
     
        public FileConfigManagerWithFileWatcher(Func<string> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
            : base(pathCreator, serializer, encoding)
        {
        }
     
        protected override void LazyInitialize()
        {
            base.LazyInitialize();
     
            // when the path is created, the watcher should be initialize at the same time
            watcher = new FileWatcher(this.Path, FileChanged);
            // just start watching the file
            watcher.StartWatching();
        }
     
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // disposes the watcher
                this.watcher.Dispose();
                this.watcher = null;
            }
     
            base.Dispose(disposing);
        }
     
        private void FileChanged(object sender, FileSystemEventArgs args)
        {
            lock (this.SyncRoot)
            {
                this.watcher.StopWatching();
                try
                {
                    // note: here making the cuurent thread sleeping a litle while to avoid exception throwed by watcher
                    Thread.Sleep(10);
                    // reload the config from file
                    this.Config = this.Serializer.DeserializeFromFile(this.Path, this.Encoding);
                }
                catch (Exception)
                {
                    // ignore it
                }
                finally
                {
                    this.watcher.StartWatching();
                }
            }
        }
    }
    internal sealed class FileConfigManagerWithCacheDependency<T> : DefaultFileConfigManager<T>
        where T : class, new()
    {
        const string KeyPrefix = "FileConfig:";
     
        public FileConfigManagerWithCacheDependency(Func<string> pathCreator, FileConfigSerializer<T> serializer, Encoding encoding)
            : base(pathCreator, serializer, encoding)
        {
        }
     
        protected override T Config
        {
            get
            {
                return HttpRuntime.Cache[KeyPrefix + this.Path] as T;
            }
            set
            {
                // if not null, update the cache value
                if (value != null)
                {
                    HttpRuntime.Cache.Insert(KeyPrefix + this.Path, value, new CacheDependency(this.Path), DateTime.
  • 相关阅读:
    程序员常用资源工具集合(建议收藏)
    程序员常用资源工具集合(建议收藏)
    16个烧光你脑细胞的悖论
    2.2_模型的选择
    2.1_Scikit-learn数据集
    Sklearn数据集与机器学习
    1.4_数据的特征选择
    1.4_特征的选择
    1.3_数据的特征预处理
    1.2_数据的特征抽取
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2305641.html
Copyright © 2011-2022 走看看