一个项目,
有一些配置,
不想写死,想用一个配置文件管理,需要读写这个配置文件。
写完了之后,看着一大堆代码,进入了反思,“我是不是自我矛盾了,说了不想写死,还是写了一个死的配置文件读写类,能不能通用一点呢,能不能搞个单例模式控制一下全局访问点呢,……“
肯定能,通用的单例实现,我见过,懒得写了,直接网上搜索了一下 :) 。
通用呢,我也不想太发散,做了如下约定:
配置文件以json文件存放在App_Data文件夹下(我现在做的是ASP.NET MVC项目,其它项目,后续再适当调整吧,大同小异),
然后,每个配置文件名,就用配置类的类名。
基本上整体代码都没什么问题,只有一个问题,让我搞了关天,那就是:
之前,我把初始化的代码,即对json配置文件反序列化的代码写在配置类实例化后就会执行的地方(比如默认构造函数内,或者是会由默认构造函数调用的其它函数内),会导到JSON反序列化时,发生异常,
进入一个死循环”实例化,初始化,反序列化,……“。
后来我把这个初始化的代码单独拿出来,在单例实例化之后调用,而不是具体配置类实例化的地方调用,从而解决了这个问题。
看着这些文字,有点晕,算了,直接上代码吧。
//用于实现通用单例和隔离类初始化方法的代码 /// <summary> /// 不支持非公共的无参构造函数的 /// </summary> /// <typeparam name="T"></typeparam> public abstract class BaseInstance1<T> where T : class, new() { private readonly static object lockObj = new object(); private static T instance = null; public static T Instance { get { if (instance == null) { lock (lockObj) { if (instance == null) { instance = new T(); } } } return instance; } } } /// <summary> /// 支持非公共的无参构造函数的 /// </summary> /// <typeparam name="T"></typeparam> public class BaseInstance2<T> where T : class, IInitable //new(),new不支持非公共的无参构造函数 { /* * 单线程测试通过! * 多线程测试通过! * 根据需要在调用的时候才实例化单例类! */ private static T _instance; private static readonly object SyncObject = new object(); public static T Instance { get { if (_instance == null)//没有第一重 singleton == null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步, //非常耗费性能 增加第一重singleton ==null 成立时的情况下执行一次锁定以实现线程同步 { lock (SyncObject) { if (_instance == null)//Double-Check Locking 双重检查锁定 { //_instance = new T(); //需要非公共的无参构造函数,不能使用new T() ,new不支持非公共的无参构造函数 _instance = (T)Activator.CreateInstance(typeof(T), true); //第二个参数防止异常:“没有为该对象定义无参数的构造函数。” _instance.Init(); } } } return _instance; } } public static void SetInstance(T value) { _instance = value; } } public interface IInitable { /// <summary> /// 带有初始化方法 /// </summary> void Init(); }
//配置基类 /// <summary> /// 基础配置类,定义了配置读取的通用操作。 /// </summary> /// <typeparam name="T"></typeparam> public abstract class BaseConfig<T> : BaseInstance2<T> where T : class , IInitable //, new() { private string filepath = null; private ILog logger = null; /// <summary> /// /// </summary> public BaseConfig() { filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", $"{typeof(T).Name}.json"); } /// <summary> /// 初始化 /// </summary> public virtual void Init() { try { logger = LoggerConfig.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); //read config file if (File.Exists(filepath)) { var josnstr = File.ReadAllText(filepath, System.Text.Encoding.UTF8); var cfg = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(josnstr); Clone(cfg); } } catch (Exception ex) { logger.Error("读取SysConfig配置文件时报错", ex); } } private void Clone(T config) { var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var p in props) { p.SetValue(this, p.GetValue(config)); } } /// <summary> /// 保存修改 /// </summary> public void Save() { try { //if (File.Exists(filepath)) { string jsonstr = Newtonsoft.Json.JsonConvert.SerializeObject(this); File.WriteAllText(filepath, jsonstr); } } catch (Exception ex) { logger.Error("保存配置文件时报错", ex); } } }
/// <summary> /// 系统配置 /// </summary> public class SysConfig : BaseConfig<SysConfig>, IInitable { private SysConfig() { } public override void Init() { base.Init(); } /// <summary> /// 配置字段1 /// </summary> public int XXXCount { get; set; } = 5; /// <summary> /// 配置字段2 /// </summary> public int YYYCount { get; set; } = 6; }
//测试代码 public ActionResult ShowSysConfig() { //var cfg1 = new SysConfig(); var cfg = SysConfig.Instance; var jsonstr = Newtonsoft.Json.JsonConvert.SerializeObject(cfg); return Content(jsonstr, "application/json"); } public ActionResult ChangeSysConfig() { var cfg = SysConfig.Instance; cfg.XXXCount += 1; cfg.YYYCount += 1; SysConfig.Instance.Save(); return ShowSysConfig(); }