通过自定义配置实现插件式设计
軟件設(shè)計(jì)有一句話叫做“約定優(yōu)于配置”,很多人將其作為拒絕配置的理由。但是,“約定”和“配置”的使用,都有個(gè)度的問題。我不贊為了所謂的擴(kuò)展性,為你的應(yīng)用設(shè)計(jì)一套只有你自己才能看懂的配置體系。但是,在很多場景中,配置是提供應(yīng)用靈活度的首要甚至是唯一途徑。對(duì)于框架的設(shè)計(jì)者來說,對(duì)于配置的駕馭是一項(xiàng)基本的技能。
可能你很少使用自定義配置,可能你理解的自定義配置僅僅限于AppSetting,不過我想你應(yīng)該對(duì)于System.Configuration這個(gè)命名空間下的幾個(gè)基本的類型有基本的了解。比如ConfigurationSection、ConfigurationElement、ConfigurationElementCollection等。本篇文章不會(huì)介紹關(guān)于System.Configuration的基礎(chǔ)知識(shí),而是通過一個(gè)簡單的例子為你講述一些所謂“高級(jí)”的知識(shí)點(diǎn),比如“不可識(shí)別配置元素的動(dòng)態(tài)解析”。(源代碼從這里下載)
目錄
一、通過自定義配置實(shí)現(xiàn)的最終效果
二、相關(guān)配置類型的定義
三、兩個(gè)重要的類型:NameTypeConfigurationElement和NameTypeConfigurationElementCollection<T>
四、ResourceProviderFactory的定義
五、補(bǔ)充
一、通過自定義配置實(shí)現(xiàn)的最終效果
為了讓大家對(duì)自定義配置的作用有一個(gè)深刻的映像,我們先來給出一個(gè)簡單的例子。我們采用在《.NET的資源并不限于.resx文件,你可以采用任意存儲(chǔ)形式》中介紹的關(guān)于自定義ResourceManager以實(shí)現(xiàn)對(duì)多種資源存儲(chǔ)形式的支持。現(xiàn)在只關(guān)注與資源的讀取,我們將基于不同存儲(chǔ)形式的資源讀取操作實(shí)現(xiàn)在相應(yīng)的ResourceProovider中,它們實(shí)現(xiàn)如下一個(gè)簡單的IResourceProvider接口。
1: public interface IResourceProvider 2: { 3: object GetObject(string key); 4: }然后我們創(chuàng)建兩個(gè)具體的ResourceProvider:DbResourceProvider和XmlResourceProvider,它們分別基于數(shù)據(jù)庫表和XML文件的資源存儲(chǔ)形式。DbResourceProvider需要連接數(shù)據(jù)庫,需要引用配置的連接字符串,所以有一個(gè)ConnectionStringName屬性;而XmlResourceProvider需要訪問具體的XML文件,FileName屬性表示文件路徑。
1: [ConfigurationElementType(typeof(DbResourceProviderConfigurationElement))] 2: public class DbResourceProvider : IResourceProvider 3: { 4: public string ConnnectionStringName { get; private set; } 5: public DbResourceProvider(string connectionStringName) 6: { 7: this.ConnnectionStringName = connectionStringName; 8: } 9: public object GetObject(string key) 10: { 11: throw new NotImplementedException(); 12: } 13: public override string ToString() 14: { 15: return string.Format("{0}\n\tConncectionString Name:{1}", typeof(DbResourceProvider).FullName, this.ConnnectionStringName); 16: } 17: } 18:? 19: [ConfigurationElementType(typeof(XmlResourceProviderConfigurationElement))] 20: public class XmlResourceProvider : IResourceProvider 21: { 22: public string FileName { get; private set; } 23: public XmlResourceProvider(string fileName) 24: { 25: this.FileName = fileName; 26: } 27: public object GetObject(string key) 28: { 29: throw new NotImplementedException(); 30: } 31: public override string ToString() 32: { 33: return string.Format("{0}\n\tFile Name:{1}", typeof(XmlResourceProvider).FullName, this.FileName); 34: } 35: }具體使用哪個(gè)ResourceProvider,通過配置來決定。整個(gè)配置定義在<artech.resources>配置節(jié)中,該配置節(jié)具有一個(gè)<providers>子節(jié)點(diǎn),它定義了一系列ResourceProvider的列表。每個(gè)ResourceProvider配置具有兩個(gè)相同的屬性:Name和Type,以及一些自己專屬的配置屬性(比如DbResourceProvider的connectionStringName,XmlResourceProvider的fileName)。至于默認(rèn)采用哪個(gè)Provider,則通過配置節(jié)的defaultProvider屬性來決定。在本例中,我們默認(rèn)采用的是DbProvider。
1: <?xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <configSections> 4: <section name="artech.resources" type="Artech.Resources.Configuration.ResourceSettings,Artech.CustomConfiguration"/> 5: </configSections> 6: <artech.resources defaultProvider="DbProvider"> 7: <providers> 8: <add name="DbProvider" type="Artech.Resources.DbResourceProvider, Artech.CustomConfiguration" connectionStringName="LocalSqlServer"/> 9: <add name="XmlProvider" type="Artech.Resources.XmlResourceProvider, Artech.CustomConfiguration" fileName="C:\resources.xml"/> 10: </providers> 11: </artech.resources> 12: </configuration>現(xiàn)在我們有一個(gè)ResourceProviderFactory的工廠類來幫助我們根據(jù)配置創(chuàng)建默認(rèn)的ResourceProvider,或者創(chuàng)建指定名稱的ResourceProvider。現(xiàn)在我們按照如下的方式使用ResourceProviderFactory。
1: static void Main(string[] args) 2: { 3: IResourceProvider resourceProvider = ResourceProviderFactory.GetResourceProvider(); 4: Console.WriteLine(resourceProvider); 5: Console.WriteLine(); 6:? 7: resourceProvider = ResourceProviderFactory.GetResourceProvider("XmlProvider"); 8: Console.WriteLine(resourceProvider); 9: Console.WriteLine(); 10:? 11: resourceProvider = ResourceProviderFactory.GetResourceProvider("DbProvider"); 12: Console.WriteLine(resourceProvider); 13: Console.WriteLine(); 14: }輸出結(jié)果:
1: Artech.Resources.DbResourceProvider 2: ConncectionString Name:LocalSqlServer 3:? 4: Artech.Resources.XmlResourceProvider 5: File Name:C:\resources.xml 6:? 7: Artech.Resources.DbResourceProvider 8: ConncectionString Name:LocalSqlServer接下來我們就來介紹整個(gè)配置體系,以及ResourceProviderFactory的實(shí)現(xiàn)。
二、相關(guān)配置類型的定義
我們現(xiàn)在來看看與配置相關(guān)的類型的定義。整個(gè)配置節(jié)定義在如下一個(gè)ResourceSettings的類中,它直接繼承自ConfigurationSection。ResourceSettings具有兩個(gè)配置屬性:DefaultProvider和Providers,分別代表<artech.resources>的defaultProvider屬性和<providers>子節(jié)點(diǎn)。
1: public class ResourceSettings: ConfigurationSection 2: { 3: [ConfigurationProperty("defaultProvider", IsRequired = true)] 4: public string DefaultProvider 5: { 6: get{return (string)this["defaultProvider"];} 7: set{this["defaultProvider"] = value;} 8: } 9: [ConfigurationProperty("providers", IsRequired = true)] 10: public NameTypeElementCollection<ResourceProviderConfigurationElement> Providers 11: { 12: get{return (NameTypeElementCollection<ResourceProviderConfigurationElement>)this["providers"];} 13: set{this["providers"] = value;} 14: } 15: public static ResourceSettings GetConfiguration() 16: { 17: return (ResourceSettings)ConfigurationManager.GetSection("artech.resources"); 18: } 19: }屬性Providers是一個(gè)名稱為NameTypeElementCollection<T>的泛型類型。從名稱我們不難看出,這是一個(gè)集合類型,代表配置的ResourceProvider集合。而基于ResourceProvider的配置定義在如下一個(gè)ResourceProviderConfigurationElement抽象類中。該類繼承自我們自定義的NameTypeConfigurationElement類型,具有一個(gè)CreateProvider抽象方法用于創(chuàng)建相應(yīng)的ResourceProvider。
1: public abstract class ResourceProviderConfigurationElement: NameTypeConfigurationElement 2: { 3: public abstract IResourceProvider CreateProvider(); 4: }DbResourceProvider和XmlResourceProvider具有各自的ResourceProviderConfigurationElement,分別為DbResourceProviderConfigurationElement和XmlResourceProviderConfigurationElement。
1: public class DbResourceProviderConfigurationElement : ResourceProviderConfigurationElement 2: { 3: [ConfigurationProperty("connectionStringName", IsRequired = true)] 4: public string ConnectionStringName 5: { 6: get{return (string)this["connectionStringName"];} 7: set{this["connectionStringName"] = value;} 8: } 9: public override IResourceProvider CreateProvider() 10: { 11: return new DbResourceProvider(this.ConnectionStringName); 12: } 13: } 14:? 15: public class XmlResourceProviderConfigurationElement : ResourceProviderConfigurationElement 16: { 17: [ConfigurationProperty("fileName", IsRequired = true)] 18: public string FileName 19: { 20: get{return (string)this["fileName"];} 21: set{this["fileName"] = value;} 22: } 23: public override IResourceProvider CreateProvider() 24: { 25: return new XmlResourceProvider(this.FileName); 26: } 27: }三、兩個(gè)重要的類型:NameTypeConfigurationElement和NameTypeConfigurationElementCollection<T>
接下來介紹兩個(gè)重要的類型,第一個(gè)是ResourceProviderConfigurationElement的基類:NameTypeConfigurationElement。顧名思義,NameTypeConfigurationElement就是具有兩個(gè)基本配置屬性Name和Type的配置元素(ConfigurationElement),其定義如下。方法DeserializeElement定義出來用于解決非識(shí)別配置項(xiàng)的反序列化問題。
1: public class NameTypeConfigurationElement : ConfigurationElement 2: { 3: [ConfigurationProperty("name", IsRequired = true, IsKey = true)] 4: public string Name 5: { 6: get{return (string)this["name"];} 7: set{this["name"] = value;} 8: } 9: [ConfigurationProperty("type", IsRequired = true)] 10: public string TypeName 11: { 12: get{return (string)this["type"];} 13: set{this["type"] = value;} 14: } 15: public Type Type 16: { 17: get{return Type.GetType(this.TypeName);} 18: } 19: public void DeserializeElement(XmlReader reader) 20: { 21: base.DeserializeElement(reader, false); 22: } 23: }另一個(gè)類型就是NameTypeConfigurationElement的配置元素集合(ConfigurationElementCollection):NameTypeElementCollection<T>。應(yīng)該說它是整個(gè)配置體系的核心,其全部定義如下所示。
1: public class NameTypeElementCollection<T> : ConfigurationElementCollection where T : NameTypeConfigurationElement 2: { 3: protected override ConfigurationElement CreateNewElement() 4: { 5: return Activator.CreateInstance<T>(); 6: } 7: protected override object GetElementKey(ConfigurationElement element) 8: { 9: return (element as NameTypeConfigurationElement).Name; 10: } 11: protected virtual Type RetrieveConfigurationElementType(XmlReader reader) 12: { 13: Type configurationElementType = null; 14: if (reader.AttributeCount > 0) 15: { 16: for (bool go = reader.MoveToFirstAttribute(); go; go = reader.MoveToNextAttribute()) 17: { 18: if ("type".Equals(reader.Name)) 19: { 20: Type providerType = Type.GetType(reader.Value, false); 21: Attribute attribute = Attribute.GetCustomAttribute(providerType, typeof(ConfigurationElementTypeAttribute)); 22: if (attribute == null) 23: { 24: throw new ConfigurationErrorsException("No ConfigurationElementTypeAttribute is applied."); 25: } 26: configurationElementType = ((ConfigurationElementTypeAttribute)attribute).ConfigurationElementType; 27: break; 28: } 29: } 30: reader.MoveToElement(); 31: } 32: return configurationElementType; 33: } 34: protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader) 35: { 36: if (base.AddElementName.Equals(elementName)) 37: { 38: Type configurationElementType = this.RetrieveConfigurationElementType(reader); 39: var currentElement = (T)Activator.CreateInstance(configurationElementType); 40: currentElement.DeserializeElement(reader); 41: base.BaseAdd(currentElement, true); 42: return true; 43: } 44: return base.OnDeserializeUnrecognizedElement(elementName, reader); 45: } 46: public T GetConfigurationElement(string name) 47: { 48: return (T)this.BaseGet(name); 49: } 50: }對(duì)于配置我們應(yīng)該有這樣的認(rèn)識(shí):我們通過相應(yīng)的類型來定義配置文件中的某個(gè)XML元素,在進(jìn)行讀取的時(shí)候?qū)嶋H上就是一個(gè)反序列化的工作。而對(duì)于成功進(jìn)行序列化和反序列化,其根本前提是確定目標(biāo)類型,因?yàn)轭愋兔枋隽嗽獢?shù)據(jù)。帶著這個(gè)結(jié)論再來看看我們的以XML表示的配置和ResourceSettings的定義,我們會(huì)發(fā)現(xiàn)一個(gè)問題:ResourceSetting的Providers屬性的類型是NameTypeElementCollection<ResourceProviderConfigurationElement>,配置元素類型ResourceProviderConfigurationElement是一個(gè)抽象類型。而我們需要將具體的ResourceProvider配置反序列化成DbResourceProviderConfigurationElement和XmlResourceProviderConfigurationElement,而整個(gè)配置系統(tǒng)似乎找不到這個(gè)兩個(gè)類型的影子。如果不能預(yù)先確定配置元素需要反序列化成的真實(shí)類型,整個(gè)配置的讀取將會(huì)失敗。具體來說,它不能識(shí)別DbProvider元素的connectionStringName屬性,和XmlProvider的fileName屬性,因?yàn)榛怰esourceProviderConfigurationElement沒有相關(guān)屬性的定義。
既然在默認(rèn)情況下具體ResourceProvider的配置元素不能被反序列化,它們屬于不可識(shí)別元素(Unrecognized Element),那么我們只要手工對(duì)其實(shí)施反序列化,具體做法就是重寫ConfigurationElementCollection的OnDeserializeUnrecognizedElement方法。但是即使手工進(jìn)行反序列化,也需要確定具體的配置元素類型,這又如何解決呢?如果你足夠仔細(xì)的話,在定義DbResourceProvider和XmlResourceProvider的時(shí)候,在類上面應(yīng)用了一個(gè)特殊的自定義特性:ConfigurationElementTypeAttribute,它建立起了具體ResourceProvider和對(duì)應(yīng)配置元素之間的匹配關(guān)系。
1: [ConfigurationElementType(typeof(DbResourceProviderConfigurationElement))] 2: public class DbResourceProvider : IResourceProvider 3: { 4: //... 5: }而這個(gè)ConfigurationElementTypeAttribute定義非常簡單,僅僅定義一個(gè)用于表示配置元素類型的ConfigurationElementType屬性,該屬性在構(gòu)造函數(shù)中初始化。
1: [AttributeUsage( AttributeTargets.Class)] 2: public class ConfigurationElementTypeAttribute: Attribute 3: { 4: public Type ConfigurationElementType { get; private set; } 5: public ConfigurationElementTypeAttribute(Type configurationElementType) 6: { 7: this.ConfigurationElementType = configurationElementType; 8: } 9: }由于每個(gè)具體的ResourceProvider都具有這樣一個(gè)ConfigurationElementTypeAttribute來指定對(duì)應(yīng)的ConfigurationElement類型,那么我們就可以反射來為反序列化確定配置元素的目標(biāo)類型了。這樣的操作實(shí)現(xiàn)在RetrieveConfigurationElementType方法中。
四、ResourceProviderFactory的定義
NameTypeElementCollection<T>通過重寫OnDeserializeUnrecognizedElement方法,以及借助于ConfigurationElementTypeAttribute特性,解決了對(duì)不可識(shí)別元素的解析問題。而具體的ResourceProviderConfigurationElement都實(shí)現(xiàn)了CreateProvider方法來創(chuàng)建對(duì)應(yīng)的ResourceProvider,那么ResourceProviderFactory的實(shí)現(xiàn)就非常簡單了。
1: public static class ResourceProviderFactory 2: { 3: public static IResourceProvider GetResourceProvider() 4: { 5: ResourceSettings settings = ResourceSettings.GetConfiguration(); 6: return GetResourceProvider(settings.DefaultProvider); 7: } 8: public static IResourceProvider GetResourceProvider(string name) 9: { 10: ResourceSettings settings = ResourceSettings.GetConfiguration(); 11: return settings.Providers.GetConfigurationElement(name).CreateProvider(); 12: } 13: }五、補(bǔ)充
經(jīng)常關(guān)注我博客朋友應(yīng)該知道本人對(duì)微軟開源框架EnterLib有一定的了解。熟悉EnterLib的朋友經(jīng)常詬病于它繁瑣的配置,這確實(shí)是一個(gè)問題。不過這從另一個(gè)方面說明了EnterLib底層配置系統(tǒng)的強(qiáng)大,不然很難支持如此復(fù)雜的配置。對(duì)于學(xué)習(xí)自定義配置,了解EnterLib配置體系的實(shí)現(xiàn)是一個(gè)不錯(cuò)的途徑。實(shí)際上,本篇文章關(guān)于“不可識(shí)別配置元素的解析”的解決方案就是來源于EnterLib。
作者:蔣金楠
微信公眾賬號(hào):大內(nèi)老A
微博:www.weibo.com/artech
如果你想及時(shí)得到個(gè)人撰寫文章以及著作的消息推送,或者想看看個(gè)人推薦的技術(shù)資料,可以掃描左邊二維碼(或者長按識(shí)別二維碼)關(guān)注個(gè)人公眾號(hào)(原來公眾帳號(hào)蔣金楠的自媒體將會(huì)停用)。
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。 原文鏈接
總結(jié)
以上是生活随笔為你收集整理的通过自定义配置实现插件式设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信键盘 iOS 版输入法 1.0.4
- 下一篇: SSM高级整合_非Maven控制版本下S