最近在优化项目中的配置文件,苦思冥想了n种解决方案,就连晚上睡觉脑子里也是Config配置节点。好吧,也许有的人会疑问,为什么要优化或者说整合项目中的配置文件呢?说白了,也是项目前期没有对配置文件有个很好的总体考虑,当项目越来越大,与其他系统之间又紧密联系在一起,你会发现项目中无论是自身的配置还是第三方的配置都很多,如果之前没有一个很好的规划,配置节点会放的到处都是,而且是毫无章法,根本区分不出那一个配置节点是哪一个模块的,这样就显得很凌乱。处于这样一个背景下,所以我们要优化配置文件,使其分块放置,看起来一目了然。于是乎我又不知道死了多少脑细胞,好吧,谁让咱年轻呢,有的就是脑细胞,早上两个鸡蛋饼后开始了我迷茫的思考,正想的起劲呢,旁边一破孩问我中午吃什么,我才意识到该午餐了(一到午餐时间大家都在纠结午餐吃什么),算了,不想了,将砂锅进行到底吧,两大荤把脑细胞补回来。饭毕,继续沉浸在一个人的程序世界。如此过了几天,也尝试做了一个优化方案,灵感来源于log4net(一个开源日志框架)对配置的实现方式,其间也研究了一下log4net的开源代码,收获颇多。

   废话不多说了,进入正题,前面说过我们是配置的优化,主要实现以下功能: 

   1.配置节点的整合,使配置项分模块放置,达到清晰明了的目的,例如系统配置、工作流配置、第三方配置等等。

   2.使开发人员使用配置项简单易用。

   3.在配置文件中的属性值一旦被修改,则不需要重新启动服务去加载配置文件,程序会自动加载配置文件。

   我优化后的部分配置项如下,一个配置文件中只有一个<configs></configs>节点,其中可以包括多个<config></config>节点,每一个<config></config>节点会有一个相对应的实体类与之对应,也就是说一个<config></config>节点就表示一个模块的配置,节点中的name命名为类名、type是类的类型。而<config></config>节点下的众多<property />节点则是当前模块的配置项也是实体类的属性。如下所示IsTestMode就是系统配置模块的一个配置项,这样就很清晰的把配置项分模块管理。

View Code
<?xml version="1.0" encoding="utf-8" ?>
<configs>
  <config name="SystemConfig" description="系统配置" type="ConfigurationCollection.ServerConfig.SystemConfig, ConfigurationCollection, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    <property key="IsTestMode" value="true"/>
    <property key="TestModeZoneNo" value="93"/>
    <property key="TestModeOrgId" value="9301"/>
    <property key="IsVerifyMac" value="false"/>
    <property key="SessionRecorderInDebugMode" value="true"/>
    <property key="ServerName" value="AstCoreService1"/>
    <property key="HistoryDbName" value="FES_AST_H"/>
    <property key="ExternalDbName" value="IMPORTDATA"/>
  </config>
  
  <config name="IPPConfig" description="IPP配置" type="ConfigurationCollection.ServerConfig.IPPConfig, ConfigurationCollection, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
    <property key="RecvMsgUseTestCase" value="false"/>
    <property key="LocalIPV4Scope" value="10."/>
    <property key="MainFrameTargetAddress" value="10.112.18.58"/>
    <property key="TcpCommTimeout" value="60000"/>
    <MainFrameService key="MainFrameService" dslVersion="1.0.0.0" Id="c9c64477-70fd-4089-a988-b63600fe663e" xmlns="http://schemas.microsoft.com/dsltools/TradeDesigner">
      <Service name="SPD_IPPSRV0" channel="SPDTCP" server="10.112.18.58:6005" servicetype=""/>
      <Service name="SPD_IPPSRV1" channel="SPDTCP" server="10.112.18.58:6005" servicetype=""/>
      <Service name="SPD_IPPSRV3" channel="SPDTCP" server="10.112.5.78:8991" servicetype=""/>
    </MainFrameService>
  </config>
</configs>

  上面介绍了优化后的配置文件结构,我们又如何加载到我们的应用当中去呢,下面是加载配置文件组件的结构,Config中是加载配置文件的入口、Util中是具体实现如何加载配置文件到具体的实体类、ClientConfig和ServrConfig是分别放客户端和服务端配置文件对应的实体类。

   实体类的创建,上面说过一个配置模块对应一个实体类,而<property />节点则是当前模块的配置项也就是实体类的属性,这个我定义了静态属性,并且把set设置为private,就是不能让开发人员去修改公共的配置项。如下所示定义了配置文件中第一个<config></config>节点,也就是系统配置的实体类:

View Code
    public class SystemConfig
    {
        public static bool IsTestMode { get; private set; }

        public static int TestModeZoneNo { get; private set; }

        public static string TestModeOrgId { get; private set; }

        public static string IsVerifyMac { get; private set; }

        public static string SessionRecorderInDebugMode { get; private set; }

        public static string ServerName { get; private set; }

        public static string HistoryDbName { get; private set; }

        public static string ExternalDbName { get; private set; }
    }

    一切就绪只欠东风,下面说说配置文件是怎样加载到实体类中的,首先引用上面的项目或者生成的dll,再利用Config中入口方法加载配置文件,如下,这里ServerConfig.config是配置文件的名称,它放在程序运行的根目录下的Configs文件夹下:

View Code
ConfigurationCollection.Config.XmlConfigurator.Configure(new System.IO.FileInfo(".\\Configs\\ServerConfig.config"));

   读取配置文件只是第一步,后面将讲述如何把配置文件转化成对应的实体类。首先读取配置文件后,我会去解析XML,把一个<configs></configs>节点下的多个<config></config>模块分组处理,就拿系统配置(SystemConfig)举例,当解析到SystemConfig模块时,我会去拿<config></config>中的type,然后创建对应的实例,同时分别解析<config></config>节点下的<property />节点,<property />节点的key就是属性名,因此就可以拿上面创建的实例去反射查找当前属性,并得到实体类中当前属性的类型,,<property />节点中的value值即是属性的值,根据反射得到的属性类型,再把value值转换成对应的类型即可(这里的转化可以自定义,自定代码在Util中实现,也可以用.net自带的一些方法转化,例如Parse)。说白了,就是利用.net的反射机制实现。代码片段如下:

View Code
private void SetParameter(XmlElement element, object target)
        {
            string key = element.GetAttribute(KEY_ATTR);

            if (key == null || key.Length == 0)
            {
                key = element.LocalName;
            }

            Type targetType = target.GetType();
            Type propertyType = null;

            PropertyInfo propInfo = null;
            MethodInfo methInfo = null;

            propInfo = targetType.GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static);
            if (propInfo != null && propInfo.CanWrite)
            {
                propertyType = propInfo.PropertyType;
            }
            else
            {
                propInfo = null;

                methInfo = FindMethodInfo(targetType, key);

                if (methInfo != null)
                {
                    propertyType = methInfo.GetParameters()[0].ParameterType;
                }
            }

            if (propertyType == null)
            {
                //LogLog.Error(declaringType, "XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target.ToString() + "]");
            }
            else
            {
                string propertyValue = null;

                if (element.GetAttributeNode(VALUE_ATTR) != null)
                {
                    propertyValue = element.GetAttribute(VALUE_ATTR);
                }
                else if (element.HasChildNodes)
                {
                    // 属性下面还有Node
                    foreach (XmlNode childNode in element.ChildNodes)
                    {
                        if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text)
                        {
                            if (propertyValue == null)
                            {
                                propertyValue = childNode.InnerText;
                            }
                            else
                            {
                                propertyValue += childNode.InnerText;
                            }
                        }
                        else
                        {
                            propertyValue = element.OuterXml;
                        }
                    }
                }

                if (propertyValue != null)
                {
                    try
                    {
                        propertyValue = OptionConverter.SubstituteVariables(propertyValue, Environment.GetEnvironmentVariables());
                    }
                    catch (System.Security.SecurityException)
                    {
                        //LogLog.Debug(declaringType, "Security exception while trying to expand environment variables. Error Ignored. No Expansion.");
                    }

                    Type parsedObjectConversionTargetType = null;

                    string subTypeString = element.GetAttribute(TYPE_ATTR);
                    if (subTypeString != null && subTypeString.Length > 0)
                    {
                        try
                        {
                            Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true);

                            if (!propertyType.IsAssignableFrom(subType))
                            {
                                if (OptionConverter.CanConvertTypeTo(subType, propertyType))
                                {
                                    parsedObjectConversionTargetType = propertyType;

                                    propertyType = subType;
                                }
                                else
                                {
                                    //LogLog.Error(declaringType, "subtype [" + subType.FullName + "] set on [" + name + "] is not a subclass of property type [" + propertyType.FullName + "] and there are no acceptable type conversions.");
                                }
                            }
                            else
                            {
                                propertyType = subType;
                            }
                        }
                        catch (Exception ex)
                        {
                            //LogLog.Error(declaringType, "Failed to find type [" + subTypeString + "] set on [" + name + "]", ex);
                        }
                    }

                    object convertedValue = OptionConverter.ConvertStringTo(propertyType, propertyValue);

                    if (convertedValue != null && parsedObjectConversionTargetType != null)
                    {
                        convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType);
                    }

                    if (convertedValue != null)
                    {
                        if (propInfo != null)
                        {
                            try
                            {
                                propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
                            }
                            catch (TargetInvocationException targetInvocationEx)
                            {
                                //LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
                            }
                        }
                        else if (methInfo != null)
                        {
                            try
                            {
                                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] { convertedValue }, CultureInfo.InvariantCulture);
                            }
                            catch (TargetInvocationException targetInvocationEx)
                            {
                                //LogLog.Error(declaringType, "Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
                            }
                        }
                    }
                    else
                    {
                        //LogLog.Warn(declaringType, "Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)");
                    }
                }
                else
                {
                    object createdObject = null;

                    if (propertyType == typeof(string) && !HasAttributesOrElements(element))
                    {
                        createdObject = "";
                    }
                    else
                    {
                        Type defaultObjectType = null;
                        if (IsTypeConstructible(propertyType))
                        {
                            defaultObjectType = propertyType;
                        }

                        createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType);
                    }

                    if (createdObject == null)
                    {
                        //LogLog.Error(declaringType, "Failed to create object to set param: " + name);
                    }
                    else
                    {
                        if (propInfo != null)
                        {
                            try
                            {
                                propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
                            }
                            catch (TargetInvocationException targetInvocationEx)
                            {
                                //LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
                            }
                        }
                        else if (methInfo != null)
                        {
                            try
                            {
                                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] { createdObject }, CultureInfo.InvariantCulture);
                            }
                            catch (TargetInvocationException targetInvocationEx)
                            {
                                //LogLog.Error(declaringType, "Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
                            }
                        }
                    }
                }
            }
        }

  对于一些不规则的配置项,就直接用XML格式作为value的值,在程序中再获取具体的属性值。如下这种配置项:

View Code
    <MainFrameService key="MainFrameService" dslVersion="1.0.0.0" Id="c9c64477-70fd-4089-a988-b63600fe663e" xmlns="http://schemas.microsoft.com/dsltools/TradeDesigner">
      <Service name="SPD_IPPSRV0" channel="SPDTCP" server="10.112.18.58:6005" servicetype=""/>
      <Service name="SPD_IPPSRV1" channel="SPDTCP" server="10.112.18.58:6005" servicetype=""/>
      <Service name="SPD_IPPSRV3" channel="SPDTCP" server="10.112.5.78:8991" servicetype=""/>
    </MainFrameService>

  接下来该如何使用呢,其实很简单了,直接上料,如下是获取系统配置中的IsTestMode配置项,是不是很简单呢:

View Code
txtIsTestMode.Text = SystemConfig.IsTestMode.ToString();

  前面已经实现的配置文件的优化,但有些时候,我为了改一下配置项的属性值还要重新启动服务,尤其是多台服务器,相当麻烦,这就需要监听配置文件有没有被修改,如果修改了则重新加载配置文件,就会用到.net中的FileSystemWatcher类。代码如下实现监听配置文件:

View Code
public MainWindow()
        {
            InitializeComponent();

            // 监听配置文件
            ConfigFileWatcher();

            // 设置配置文件
            SetConfigEnvironment();
        }

/// <summary>
        /// 监听配置文件
        /// </summary>
        public void ConfigFileWatcher()
        {
            FileSystemWatcher watcher = new FileSystemWatcher();
            watcher.Path = AppDomain.CurrentDomain.BaseDirectory + "Configs\\";
            watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
            // 只监控.config文件  
            watcher.Filter = "*.config";
            // 添加事件处理器。  
            watcher.Changed += new FileSystemEventHandler(OnChanged);
            // 开始监控。  
            watcher.EnableRaisingEvents = true;
        }

        public void OnChanged(object source, FileSystemEventArgs e)
        {
            SetConfigEnvironment();
        } 
        public void SetConfigEnvironment()
        {
            // 初始化配置文件
            ConfigurationCollection.Config.XmlConfigurator.Configure(new System.IO.FileInfo(".\\Configs\\ServerConfig.config"));
        }

  到此,对配置文件的优化就结束了,由于网上对配置文件的管理方案也不多,特写出来与大家分享,如果你有什么好的方案,大家也可以一起交流。欢迎吐槽...

  写的太high,快到一点了,赶快整理整理,来一把“找你妹”睡觉了,还好明天是星期五,心情还不错,不对,已经是星期五了,555。

附源码:https://files.cnblogs.com/bhtx/ConfigSolution.rar

posted on 2013-03-15 01:08  HaiSnowTian  阅读(1884)  评论(8编辑  收藏  举报