最近在優化項目中的配置文件,苦思冥想了n種解決方案,就連晚上睡覺腦子里也是Config配置節點。好吧,也許有的人會疑問,為什么要優化或者說整合項目中的配置文件呢?說白了,也是項目前期沒有對配置文件有個很好的總體考慮,當項目越來越大,與其他系統之間又緊密聯系在一起,你會發現項目中無論是自身的配置還是第三方的配置都很多,如果之前沒有一個很好的規划,配置節點會放的到處都是,而且是毫無章法,根本區分不出那一個配置節點是哪一個模塊的,這樣就顯得很凌亂。處於這樣一個背景下,所以我們要優化配置文件,使其分塊放置,看起來一目了然。於是乎我又不知道死了多少腦細胞,好吧,誰讓咱年輕呢,有的就是腦細胞,早上兩個雞蛋餅后開始了我迷茫的思考,正想的起勁呢,旁邊一破孩問我中午吃什么,我才意識到該午餐了(一到午餐時間大家都在糾結午餐吃什么),算了,不想了,將砂鍋進行到底吧,兩大葷把腦細胞補回來。飯畢,繼續沉浸在一個人的程序世界。如此過了幾天,也嘗試做了一個優化方案,靈感來源於log4net(一個開源日志框架)對配置的實現方式,其間也研究了一下log4net的開源代碼,收獲頗多。
廢話不多說了,進入正題,前面說過我們是配置的優化,主要實現以下功能:
1.配置節點的整合,使配置項分模塊放置,達到清晰明了的目的,例如系統配置、工作流配置、第三方配置等等。
2.使開發人員使用配置項簡單易用。
3.在配置文件中的屬性值一旦被修改,則不需要重新啟動服務去加載配置文件,程序會自動加載配置文件。
我優化后的部分配置項如下,一個配置文件中只有一個<configs></configs>節點,其中可以包括多個<config></config>節點,每一個<config></config>節點會有一個相對應的實體類與之對應,也就是說一個<config></config>節點就表示一個模塊的配置,節點中的name命名為類名、type是類的類型。而<config></config>節點下的眾多<property />節點則是當前模塊的配置項也是實體類的屬性。如下所示IsTestMode就是系統配置模塊的一個配置項,這樣就很清晰的把配置項分模塊管理。

<?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>節點,也就是系統配置的實體類:

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文件夾下:

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的反射機制實現。代碼片段如下:

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的值,在程序中再獲取具體的屬性值。如下這種配置項:

<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配置項,是不是很簡單呢:

txtIsTestMode.Text = SystemConfig.IsTestMode.ToString();
前面已經實現的配置文件的優化,但有些時候,我為了改一下配置項的屬性值還要重新啟動服務,尤其是多台服務器,相當麻煩,這就需要監聽配置文件有沒有被修改,如果修改了則重新加載配置文件,就會用到.net中的FileSystemWatcher類。代碼如下實現監聽配置文件:

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。