Leo Zhang

A simple man with my own ideal

EL4.1配置文件管理浅谈(1)

一、前言

    我们知道高耦合性的代码是很不方便变更的,可能会导致牵一发而动全身,为了解耦大家想了很多方法,例如依赖注入等等,常见的做法是将这种耦合外推到配置文件,那么如何能对配置文件进行很好的组织就成了一个比较重要的部分。本文以EL4.1的配置文件管理为例子,看下他们是怎么做的。

二、设计思路

1、从配置文件说起

     先简单说一下配置文件的格式,本文所说的配置文件是指.NET程序中的客户端应用程序配置文件(App.config文件),给个简单例子先(需要注意的是,配置文件是区分大小写的,aA是不一样的)

App.config
<configuration>
  
<!--Begin 用户自定义section的定义部分-->
  
<configSections>
    
<section name="UserDefination" type="Sections.UserDefination, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null" />
    
<section name="ServerConfig" type="Sections.ServerConfig, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null"/>
  
</configSections>
  
<!--End   用户自定义section的定义部分-->

  
<!--Begin 用户自定义section的使用部分-->
  
<UserDefination name="leo" value="true"/>
  
<ServerConfig ip="http://172.22.9.13"/>
  
<!--End   用户自定义section的使用部分-->

  
<appSettings>
    
<add key = "ConnenctionString" value="Test..."/>
  
</appSettings>

</configuration>

   从例子中可以看到配置文件的几个要素:

configuration:根元素是唯一的;

configurations:用户自定义section的声明部分,我觉得可以认为是:变量要是用前需要先声明;

section:声明配置节的名字和其类型,分别由nametype组成;

UserDefinationServerConfig:对configurations中声明的section(配置节)进行使用;

appSettings:系统定义的一种section(类似的还有connectionStrings)

这里有一点很重要:appSettings要放在用户自定义section之后,否则使用ConfigurationManager类型的方法获取section时会抛出配置系统未能初始化的异常。

大概了解配置文件的组织结构后就需要知道在.NET中是如何使用配置文件的。我们使用的最多的工具就是一个静态类ConfigurationManager,它是在.net framework2.0新增的一个类型,提供对客户端应用程序配置文件的访问,包括对配置文件的读取和写入操作。

    ①、如何读出appSettings,方法很简单:

 

String str = ConfigurationManager.AppSettings["ConnenctionString"];

 

    、如何读出用户自定义section,以UserDefination为例:

  首先需要定义UserDefination类型:

ServerConfig
    public class ServerConfig : ConfigurationSection
    {
        
private const String ip = "ip";

        
public const String SectionName = "ServerConfig";

        [ConfigurationProperty(ip, IsRequired 
= true)]
        [TypeConverter(
typeof(MyUriTypeConverter))]
        
public Uri IP
        {
            
get { return (Uri)this[ip]; }
            
set { this[ip] = value; }
        }
    }

  

 

ServerConfig中有一个属性:IP,为它打上标记:

ConfigurationProperty,对应配置文件里ServerConfig配置节中的属性ip(注意大小写一定要一致J)

其次需要定义类型转换规则:

仔细观察可以看到,在配置文件中属性的值都是字符串(如:ip="http://172.22.9.13"),那么使用ConfigurationManager读取ServerConfig配置节后需要将其转换为ServerConfig类型,那么需要规则将相应配置节中的属性ip(类型为字符串)转化为ServerConfig中的属性IP (类型为Uri)(反之在写入配置文件的时候也类似)

于是定义MyUriTypeConverter类型: 

 

MyUriTypeConverter
    public class MyUriTypeConverter : ConfigurationConverterBase
    {
        
public override object ConvertTo(
System.ComponentModel.ITypeDescriptorContext context, 
System.Globalization.CultureInfo culture, 
object value,
Type destinationType)
        {
            
if (value != null)
            {
                Uri uriValue 
= value as Uri;
                
if (uriValue == null)
                {
                    
throw new ArgumentException("The value must be an uri address.");
                }

                
if (uriValue != nullreturn uriValue.ToString();
            }
            
return null;
        }

        
public override object ConvertFrom(
System.ComponentModel.ITypeDescriptorContext context, 
System.Globalization.CultureInfo culture, 
object value)
        {
            
string stringValue = (string)value;
            
if (!string.IsNullOrEmpty(stringValue))
            {
                Uri result 
= new Uri(stringValue);
                
if (result == null)
                {
                    
throw new ArgumentException("No uri found.");
                }

                
return result;
            }
            
return null;
        }
    }

 

  其中,ConvertTo表示写入配置文件时将ServerConfig实例的属性IPUri类型转换为String类型的规则,而ConvertFrom表示读取配置文件时将配置节ip的值由String类型转换为Uri类型。

     最后可以这样读取配置文件:

ServerConfig configurationSection =

ConfigurationManager.GetSection(ServerConfig.SectionName) as ServerConfig;

       如果应用程序需要对它自己的配置进行只读访问,对于用户自定义sectionMS建议使用ConfigurationManagerGetSection方法,此方法提供对当前应用程序的缓存配置值的访问,它的性能比 Configuration 类更好。 

2、配置文件的监控

     由于配置文件的作用决定了它允许人们可以随时改变其内容从而影响软件的行为模式,于是,我们需要一个监控器来随时监控配置文件的情况,一旦发现它的内容发生改变则做出相应的反应。为方便描述,我将其称为:“配置文件监控器”。通常来说,配置文件监控器会在一个单独的辅助线程上运行,在没有收到停止监控的命令之前该线程会一直处于运行状态并在某个时间间隔下不停的轮询配置文件的状态(是否被修改)

EL4.1中,有关监控器内容在程序集CommonConfiguration. Storage中。设计者抽象出一个配置文件监控器接口:IConfigurationChangeWatcher,

 

1

其中SectionName存储配置文件中被监控的配置节(Section)StartWatchingStopWatching分别用来启动和停止监控器,ConfigurationChanged事件用来执行当监控对象发生改变时需要执行的回调方法。

此外,定义了一个实现了IConfigurationChangeWatcher接口的抽象类型:ConfigurationChangeWatcher,这个类型实现了监控器的启动和停止时的行为模式,例如启动时的方法如下:
Watcher
        public void StartWatching()
        {
            
lock (lockObj)
            {
                
if (pollingThread == null)
                {
                    pollingStatus 
= new PollingStatus(true);
                    pollingThread 
= new Thread(new ParameterizedThreadStart(Poller));
                    pollingThread.IsBackground 
= true;
                    pollingThread.Name 
= this.BuildThreadName();
                    pollingThread.Start(pollingStatus);
                }
            }
        }
        
private void Poller(object parameter)
        {
            lastWriteTime 
= DateTime.MinValue;
            DateTime currentLastWriteTime 
= DateTime.MinValue;
            PollingStatus pollingStatus 
= (PollingStatus)parameter;

            
while (pollingStatus.Polling)
            {
                currentLastWriteTime 
= GetCurrentLastWriteTime();
                
if (currentLastWriteTime != DateTime.MinValue)
                {
                    
if (lastWriteTime.Equals(DateTime.MinValue))
                    {
                        lastWriteTime 
= currentLastWriteTime;
                    }
                    
else
                    {
                        
if (lastWriteTime.Equals(currentLastWriteTime) == false)
                        {
                            lastWriteTime 
= currentLastWriteTime;
                            OnConfigurationChanged();
                        }
                    }
                }
                Thread.Sleep(pollDelayInMilliseconds);        
            }
         }

 

这里的PollingStatus类型对象用来存储监控器启动或停止的状态,创建一个新的线程:pollingThread,并将其设置为后台线程,(后台线程的说明:http://msdn.microsoft.com/zh-cn/library/h339syd0(VS.80).aspx),在pollingThread中执行轮询的操作是Poller方法,通过PollingStatus类型对象控制轮询是否停止,通过抽象方法:GetCurrentLastWriteTime获取目前监控对象最后被修改的时间,然后比较之前记录的原最后被修改时间(lastWriteTime)和目前监控对象最后被修改时间(currentLastWriteTime),如果不一致则触发之前注册到ConfigurationChangeWatcher的回调方法。

最后,有一个继承了ConfigurationChangeWatcher抽象类型的具体类型:ConfigurationChangeFileWatcher,类型中规定了方法GetCurrentLastWriteTime的行为模式是:获取被监控配置文件在本地磁盘上最后被写入的时间。

顺便说一句,我觉得EL4.1中配置文件监控器的设计不仅能用于监控配置文件修改的场景还可以应用到其它基于文件修改监控的场景。

配置文件监控器的运行示意图如下:
2

3、配置文件管理机制

       首先提出一个问题:监控器所监控的对象是配置文件中的section,那么如何设计才能让监控器和section有较小的耦合性且易于灵活组织它们之间的关系?

     EL4.1中是这样做的:

     ①、建立抽象类ConfigurationSourceWatcher,如图3

它被抽象为一个section和配置文件监控器之间关系的组织者,configWatcher为当前使用的配置文件监控器,在对其构造时会指定回调方法,watchedSections为被配置文件监控器所监控的section的集合(存储section的名字),另外封装了对监控器的启动和停止操作。我认为ConfigurationSourceWatcher所扮演的角色是一个“媒介”,使得管理配置文件源的实现和配置文件监控器之间不存在直接耦合,为方便描述,在本文中,我将ConfigurationSourceWatcher叫做“配置源监视器”
3
  ②、建立抽象类BaseFileConfigurationSourceImplementation,如图4

4

 

该抽象类以配置源监视器为媒介,它的职责是:

管理ConfigSource(配置源)ConfigurationSourceWatcher(配置源监视器)的映射关系;

管理被监控section(配置节)ConfigurationSourceWatcher(配置源监视器)的映射关系;

管理section绑定事件(即某个section被更新时所要执行的后续操作)

当配置文件被修改后更新被监控的section(包括外部配置文件中被监控的section)并执行绑定到section上的事件(Event)

     配置文件可以由主配置文件和外部配置文件组成,所以对配置文件section的管理要同时考虑主配置文件和外部配置文件,一般来说主配置文件应该有一个而外部配置文件可以有很多。

关键field的说明:

    configFileWatcher

对当前应用程序的主配置文件(app.config)section进行监控的配置源监视器,该监视器的标识为:String.Empty,且唯一;

configurationFilepath:存储主配置文件所在路径;

eventHandlers

事件列表,用来存储与某section相关的事件,当然,一个section可以与多个事件相关联;

watchedConfigSourceMapping

用来映射配置源与配置源监视器之间的逻辑关系,同时也就知道了该配置源中有哪些section被哪个配置源监视器所监控;

watchedSectionMapping

用来映射配置节(section)与配置源监视器之间的逻辑关系,同时也就知道了该配置节被哪个配置源监视器所监控,从而在该配置节的配置源发生改变的时候可以移除它与原配置源监视器的映射关系或添加该配置节与新的配置源监视器的映射关系并进行监控。

 

核心method的说明:

     SetWatcherForSection

为当前section指定新的配置源,分两种情况:当某个配置源的配置源监视器存在的时候则将该配置源监视器停止,将当前section加入配置源监视器并将新的映射关系加入watchedSectionMapping,并重启配置源监视器;否则,依据新配置源的名字建立配置源监视器,将当前section加入配置源监视器并将新的映射关系加入watchedSectionMappingwatchedConfigSourceMapping中。

ConfigSourceChanged

大家回头看一下下配置文件监控器的工作原理(2,同时适用于主配置文件和外部配置文件的监控),当主配置文件被修改后会有一个调用回调方法的步骤,这个回调方法正是ConfigSourceChanged配置源监视器被创建时会将传入并绑定到该配置源监视器中的配置文件监控器上ConfigSourceChanged被触发后会做如下几件事:

1)、扫描当前存在的所有配置源监视器,分别将主配置文件的配置源监视器和外部配置文件的配置源监视器中被监控的所有section分别放在两个健/值对集合中以便更新;

2)、刷新section,用一个集合sectionsWithChangedConfigSource存储配置源发生改变的section及其新的配置源(以前存在现在被删除的section会对应一个以“__null__”标识的新配置源);用另一个集合sectionsToNotify存储主配置文件中所有被监控的section和内容存储在外部配置文件但外部配置源发生更改的section

3)、更新配置源监视器,删除无效的逻辑关系和添加新的逻辑关系;

4)、遍历步骤2中存储在集合B中的section,调用与各个section相关的回调方法。

ExternalConfigSourceChanged

ConfigSourceChanged不同,它会在外部配置源内容发生变化时被触发。

UpdateWatcherForSection

更新配置源监视器和section之间的逻辑关系,如果section被已经存在的配置源监视器所监视则删除原有关系,建立新关系,否则直接建立新关系。

 

③、分别针对默认配置文件,(App.config)和非默认配置文件(存储于任意目录的配置文件)实现SystemConfigurationSourceImplementationFileConfigurationSourceImplementation,这两个类的不同在于刷新和检测配置节有效性时的方式的不同。

、实例

1、使用默认客户端配置文件

  ①、IDEVS2008

  ②、建立解决方案,包含三部分:

  1)、引入EL4.1Common部分;

  2)、新建项目,定义两个用户自定义sectionUserDefinationServerConfig以及相应的类型转换器(需要的话)

  3)、建立控制台应用程序作为主程序,并建立App.config文件,内容如下:
App.config
<configuration>
  
<!--Begin 用户自定义section的定义部分-->
  
<configSections>
    
<section name="UserDefination" type="Sections.UserDefination, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null" />
    
<section name="ServerConfig" type="Sections.ServerConfig, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null"/>
  
</configSections>
  
<!--End   用户自定义section的定义部分-->

  
<!--Begin 用户自定义section的使用部分-->
  
<UserDefination name="leo" value="true"/>
  
<ServerConfig ip="http://172.22.9.13"/>
  
<!--End   用户自定义section的使用部分-->

  
<appSettings>
    
<add key = "ConnenctionString" value="Test..."/>
  
</appSettings>

</configuration>

  本例只有一个主配置文件,不存在外部配置文件。

2、使用非默认客户端配置文件

       开发环境与建立项目情况同上,在D(如果没有D盘可选任意盘建立并修改相应代码)建立主配置文件:config.config,在应用程序执行路径下建立外部配置文件externalserver.config,内容如下:
config.config
<configuration>
  
<configSections>    
    
<section name="UserDefination" type="Sections.UserDefination, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null" />
    
<section name="ServerConfig" type="Sections.ServerConfig, Sections, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null" />
  
</configSections>

  
<UserDefination name="leo" value="true"/>
  
<ServerConfig  configSource="externalserver.config" />
</configuration>

 externalserver.config

<ServerConfig ip="http://172.22.9.14" />

 

 

 

3、本文代码可以单击这里下载

posted on 2010-04-12 21:24  Leo Zhang  阅读(1196)  评论(2编辑  收藏

导航