[转载].NET的资源并不限于.resx文件,你可以采用任意存储形式

为了构建一个轻量级的资源管理框架以满足简单的本地化(Localization)的需求,我试图直接对现有的Resource编程模型进行扩展。虽然最终没能满足我们的需求,但是这两天也算对.NET如何进行资源的存取进行了深入的学习,所以将我对此的认识通过博文的方式与诸位分享。在本篇文章中,我会通过自定义ResourceManager让资源的存储形式不仅仅局限于.ResX文件,你可以根据需要实现任意的存储方式,比如结构化的XML数据库表,甚至是通过远程访问获取资源。(文中的例子从这里下载)

一、从添加资源文件说起
二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter
三、自定义BinaryResourceManager管理单独二机制资源文件

一、从添加资源文件(.resx文件)说起

说起资源,你首先想到的肯定是通过VS添加的扩展名为.resx的资源文件。在这个资源文件中,你不但可以添加单纯的文本资源条目,也可以添加图片、图标、文本文件以及其它类型文件。 不但如此,当你在.resx文件中定义任意类型资源条目的时候,默认定义的代码生成器会为你生成对应的托管代码,使你可以采用强类型编程的方式获取某个条目。

image

比如说,如果你在一个名称为Resources.resx的资源文件中定义了如上图所示的两个字符串资源条目,默认的代码生成器或为你生成如下的代码。

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
    
    private static global::System.Resources.ResourceManager resourceMan;
    
    private static global::System.Globalization.CultureInfo resourceCulture;
    
    [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal Resources() {
    }    
 
    [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
    internal static global::System.Resources.ResourceManager ResourceManager {
        get {
            if (object.ReferenceEquals(resourceMan, null)) {
                global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demo.Properties.Resources", typeof(Resources).Assembly);
                resourceMan = temp;
            }
            return resourceMan;
        }
    }   
 
    [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
    internal static global::System.Globalization.CultureInfo Culture {
        get {
            return resourceCulture;
        }
        set {
            resourceCulture = value;
        }
    }    
 
    internal static string Greeting4Chris {
        get {
            return ResourceManager.GetString("Greeting4Chris", resourceCulture);
        }
    }
    internal static string Greeting4NewYear {
        get {
            return ResourceManager.GetString("Greeting4NewYear", resourceCulture);
        }
    }
}

那么你就可以通过生成的这个Resources类(和资源文件同名)的对应的静态只读属性获取对应的值。

   1: var greeting4Chris = Resources.Greeting4Chris;
   2: var greeting4NewYear = Resources.Greeting4NewYear;

从通过代码生成器生成出来的Resources代码,我们可以看出Greeting4Chris和Greeting4NewYear这两个属性的实现是直接通过一个类型为ResourceManager对象的GetString方法获取的。那么ResourceManager在背后是通过怎样的机制进行资源文件的读取的呢?

二、ResourceManager、ResourceSet、ResourceReader与ResourceWriter

ResourceManager应该是.NET资源编程模型的核心,也可以说是整个资源编程模型的外观类(Facade Class),它提供资源条目提取的API。ResourceManager定义在System.Resources命名空间下,我们不防先来看看ResourceManager的定义。

   1: public class ResourceManager
   2: {
   3:     public ResourceManager(Type resourceSource);
   4:     public ResourceManager(string baseName, Assembly assembly);
   5:     public ResourceManager(string baseName, Assembly assembly, Type usingResourceSet);
   6:  
   7:     public virtual object GetObject(string name);
   8:     public virtual object GetObject(string name, CultureInfo culture);
   9:     public virtual string GetString(string name);
  10:     public virtual string GetString(string name, CultureInfo culture);
  11:  
  12:     public virtual ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
  13:     protected virtual ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents);
  14:     //Others...
  15: }

虽然我们将相应的条目定义在.resx资源文件中(该文件实际上就是一个XML),但是该文件在编译的时候会变成.resources文件(二进制文件)被内嵌到程序集中,所以ResourceManager操作的实际上是内嵌在某个程序集中的.resources文件,这也是为什么在构造函数中需要指定Assembly的原因。构造函数的另一个参数BaseName表示不包括扩展名和Culture Code的.resources文件名,比如说资源文件名为Foo.en-US.resoures对应的BaseName就是Foo。

对于字符串类型的资源条目,通过GetString方法获取,其他类型的文件则通过GetObject获取。而ResourceManager的核心实际上是一个叫做GetResourceSet的方法,方法将所有的资源条目读取出来保存到一个类型为ResourceSet的对象中(该方法最终会调用受保护的方法InternalGetResourceSet)。而ResourceSet在整个资源体系中是一个重要的对象,它充当ResourceManager和物理存储的中介,下面是ResourceSet的定义。

   1: public class ResourceSet : IDisposable, IEnumerable
   2: {
   3:     public ResourceSet(Stream stream);
   4:     public ResourceSet(IResourceReader reader);
   5:     public ResourceSet(string fileName);
   6:    
   7:     public virtual Type GetDefaultReader();
   8:     public virtual Type GetDefaultWriter();
   9:  
  10:     public virtual object GetObject(string name);
  11:     public virtual object GetObject(string name, bool ignoreCase);
  12:     public virtual string GetString(string name);
  13:     public virtual string GetString(string name, bool ignoreCase);
  14:  
  15:     IEnumerator IEnumerable.GetEnumerator();
  16:     public virtual IDictionaryEnumerator GetEnumerator();
  17:     public void Dispose();
  18:     //Others...
  19: }

以持久化文件方式存储的资源最终需要加载到ResourceSet对象中,肯定需要IO操作,所以ResourceSet构造函数中参数分别是Stream、文件名和一个IResourceReader的对象。GetObject和GetString方法,不用多说你也知道是用于某个命名资源条目。由于资源条目实际上就是简单Key-Value对,所以ResourceSet仅仅需要为ResourceManager提供针对每个资源条目的迭代功能,所以ResourceSet的核心应该是返回类型为IDictionaryEnumerator虚方法GetEmunerator方法。

而ResourceSet得两个GetDefaultReader和GetDefaultWriter方法则涉及到另外两个重要的对象ResourceReaderResourceWriter,故名思义它们分别负责资源的读取和写入。在System.Resources命名空间下,它们各自具有相应的接口:IResourceReaderIResourceWriter,定义如下:

   1: public interface IResourceReader : IEnumerable, IDisposable
   2: {
   3:     void Close();
   4:     IDictionaryEnumerator GetEnumerator();
   5: } 

 

   1: public interface IResourceWriter : IDisposable
   2: {
   3:     void AddResource(string name, object value);
   4:     void AddResource(string name, string value);
   5:     void AddResource(string name, byte[] value);
   6:     void Close();
   7:     void Generate();
   8: }

到这里我们介绍了资源体系下四个重要的对象ResourceManager、ResourceSet、ResourceReader与ResourceWriter,至于他们是如何相互协作以实现对资源的读取和写入的,相信下面会给你答案。

三、自定义BinaryResourceManager管理单独二进制资源文件(.resources文件)

我们说过上述的ResourceManager仅仅提供对内嵌于某个程序集中的.resources文件的操作,如果我们直接将资源定义在一个独立的.resources文件.resx文件甚至是自定义结构的XML文件呢?在这种情况下,我们可通过自定义ResourceManager的方式来解决这个问题。为此我定义了如下一个抽象类FileResourceManager作为基于文件的ResourceManager的基类。

   1: public abstract class FileResourceManager: ResourceManager
   2: {
   3:     private string baseName;
   4:     public string Directory { get; private set; }
   5:     public string Extension { get; private set; }
   6:  
   7:     public override string BaseName
   8:     {
   9:         get{ return baseName;}
  10:     }
  11:  
  12:     public FileResourceManager(string directory, string baseName, string extension)
  13:     {
  14:         this.Directory = directory;
  15:         this.baseName = baseName;
  16:         this.Extension = extension;
  17:     }
  18:  
  19:     protected override string GetResourceFileName(CultureInfo culture)
  20:     {
  21:         string fileName = string.Format("{0}.{1}.{2}", this.baseName, culture, this.Extension.TrimStart('.'));
  22:         string path = Path.Combine(this.Directory, fileName);
  23:         if (File.Exists(path))
  24:         {
  25:             return path;
  26:         }
  27:         return Path.Combine(this.Directory, string.Format("{0}.{1}", baseName, this.Extension.TrimStart('.')));
  28:     }
  29: }

属性Directory、BaseName和Extension分别表示资源文件所在的目录、不包括Culture Code和扩展名的文件名以及扩展名。FileResourceManager集成自ResourceManager类,并重写了GetResourceFileName方法用于获取基于某种Culture的资源文件路径。

现在我们定义如下一个BinaryResourceManager用于操作单独存在的.resources文件。我自需要重写InternalGetResourceSet,返回的是基于.resources文件名创建的ResourceSet对象。

   1: public class BinaryResourceManager : FileResourceManager
   2: {
   3:     public BinaryResourceManager(string directory, string baseName)
   4:         : base(directory, baseName, ".resources")
   5:     {}
   6:  
   7:     protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
   8:     {
   9:         return new ResourceSet(this.GetResourceFileName(culture));
  10:     }
  11: }

现在我们来看看如何使用我们创建的BinaryResourceManager。由于它直接操作ResourceSet来维护资源条目列表,当我们通过指定资源文件名创建ResourceSet的时候,系统会创建一个类型为System.Resources.ResourceReader的对象来读取二进制的.resources文件并将内容写入ResourceSet对象。而.resources文件具有默认的ResourceWrtier,即System.Resources.ResourceWriter。

为了让我们的Demo能够适用于后续的自定义ResourceManager,我写了一些辅助方法,首先是预先创建资源文件的方法PrepareFiles方法。通过传入的BaseName和扩展名,我会创建三个资源文件:<BaseName>.<Extension>、<BaseName>.en-US.<Extension>和<BaseName>.zh-CN.<Extension>,第一个代码语言文化中性,后者则基于美国英语和简体中文。

   1: static void PrepareFiles(string baseName, string extension)
   2: {
   3:     var fileNames = new string[]{ 
   4:         baseName + "." + extension, 
   5:         baseName + ".en-US." + extension, 
   6:         baseName + ".zh-CN." + extension };
   7:  
   8:     Array.ForEach(fileNames, fileName =>{
   9:             if (!File.Exists(fileName)) File.Create(fileName).Dispose();});
  10: }

然后是用于资源写入操作的AddResource方法,该方法两个参数createWriter和culture表示创建IResourceWriter的委托和对应的语言文化。

   1: static void AddResource(Func<IResourceWriter> createWriter, CultureInfo culture)
   2: {
   3:     using (IResourceWriter resourceWriter = createWriter())
   4:     {
   5:         if (culture.Name.StartsWith("en"))
   6:         {
   7:             resourceWriter.AddResource("Greeting4Chris", "Merry Christmas!");
   8:             resourceWriter.AddResource("Greeting4NewYear", "Happy Chinese New Year!");
   9:         }
  10:         if (culture.Name.StartsWith("zh"))
  11:         {
  12:             resourceWriter.AddResource("Greeting4Chris",  "圣诞快乐!");
  13:             resourceWriter.AddResource("Greeting4NewYear", "新年快乐!");
  14:         }
  15:         resourceWriter.Generate();
  16:     }            
  17: }

最后是用于资源读取和输出的DisplayResource方法,该方法通过指定的ResourceManager读取当前需要文化资源并输出。而我指定了三种不同的语言文化环境:en-US、zh-CN和ja-JP。

   1: static void DisplayResource(ResourceManager resourceManager)
   2: {
   3:     Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
   4:     Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
   5:     Console.WriteLine("\t"+resourceManager.GetString("Greeting4Chris"));
   6:     Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
   7:  
   8:     Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
   9:     Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
  10:     Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris"));
  11:     Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
  12:  
  13:     Thread.CurrentThread.CurrentUICulture = new CultureInfo("ja-JP");
  14:     Console.WriteLine(CultureInfo.CurrentUICulture.EnglishName);
  15:     Console.WriteLine("\t" + resourceManager.GetString("Greeting4Chris"));
  16:     Console.WriteLine("\t" + resourceManager.GetString("Greeting4NewYear") + "\n");
  17: }        

最后我们的程序是这样的:

   1: PrepareFiles("GreetingMessages", "resources");
   2:  
   3: AddResource(() => new ResourceWriter("GreetingMessages.resources"), new CultureInfo("en-US"));
   4: AddResource(() => new ResourceWriter("GreetingMessages.en-US.resources"), new CultureInfo("en-US"));
   5: AddResource(() => new ResourceWriter("GreetingMessages.zh-CN.resources"), new CultureInfo("zh-CN"));
   6:  
   7: DisplayResource(new BinaryResourceManager("", "GreetingMessages"));

最终的输出为:

   1: English (United States)
   2:         Merry Christmas!
   3:         Happy Chinese New Year!
   4:  
   5: Chinese (Simplified, PRC)
   6:         圣诞快乐!
   7:         新年快乐!
   8:  
   9: Japanese (Japan)
  10:         Merry Christmas!
  11:         Happy Chinese New Year!

在《下篇》中,我将介绍如何通过自定义ResourceManager操作定义在.resx、XML和数据库表的资源。

 

在《上篇》中我们谈到ResourceManager在默认的情况下只能提供对内嵌于程序集的.resources资源文件的存取。为了实现对独立二进制.resources资源文件的支持,我们自定义了BinaryResoruceNManager。在本篇中我们还将创建两个自定义的ResourceManager,以实现对独立.resx资源文件自定义结构的XML资源文件的支持。(文中的例子从这里下载)

一、自定义ResXResourceManager实现对.Resx资源文件的支持
二、将资源定义在自定义结构的XML文件中
三、为XML资源存储形式定义ResourceReader和ResourceWriter
四、为XML资源存储形式定义ResourceSet
五、为XML资源存储形式定义ResourceManager
六、补充

 

一、自定义ResXResourceManager实现对.Resx资源文件的支持

较之.resources资源文件这种二进制文件,以XML形式定义的.Resx资源文件是一个纯文本文件,我们可以对其进行自由地修改,所以有时候我们直接将独立的.resx文件作为资源存储形式更利于资源内容的维护。在《上篇》中我们创建了自定义的BinaryResourceManager实现了对独立.resources资源文件的支持,这里我们仅仅需要采用相似的方式定义一个ResXResourceManager。由于.NET已经提供了支持.Resx资源文件的ResourceSet、ResourceReader和ResourceWriter,所以ResXResourceManager和BinaryResourceManager一样简单,下面是其全部定义。

   1: public class ResXResourceManager : FileResourceManager
   2: {
   3:     public ResXResourceManager(string directory, string baseName)
   4:         : base(directory, baseName, ".resx")
   5:     {}
   6:  
   7:     protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
   8:     {
   9:         return new ResXResourceSet(this.GetResourceFileName(culture));
  10:     }
  11: }

.resx文件对应的ReourceSet为ResXResourceSet,定义在System.Windows.Forms程序集中,所以在重写的InternalGetResourceSet中我们只需要返回这么一个ResXResourceSet即可。ResXResourceSet对应的ResourceReader为ResXResourceReader,而.resx文件可以通过ResXResourceWriter进行写入。

既然我们的ResXResourceManager已经创建好了,我们就可以将它应用到我们的演示程序中。演示代码如下所示,三个辅助方法PrepareFiles、AddResource和DisplayResource的实现可以参考《上篇》,后面列出的是与之前的演示完全一样的输出结果。

   1: PrepareFiles("GreetingMessages", "resx");
   2:  
   3: AddResource(() => new ResXResourceWriter("GreetingMessages.resx"), new CultureInfo("en-US"));
   4: AddResource(() => new ResXResourceWriter("GreetingMessages.en-US.resx"), new CultureInfo("en-US"));
   5: AddResource(() => new ResXResourceWriter("GreetingMessages.zh-CN.resx"), new CultureInfo("zh-CN"));
   6:  
   7: DisplayResource(new ResXResourceManager("", "GreetingMessages"));

输出结果

   1: English (United States)
   2:         Merry Christmas!
   3:         Happy Chinese New Year!
   4:  
   5: Chinese (Simplified, PRC)
   6:         圣诞快乐!
   7:         新年快乐!
   8:  
   9: Japanese (Japan)
  10:         Merry Christmas!
  11:         Happy Chinese New Year!

 

二、将资源定义在自定义结构的XML文件中

.Resx资源文件本质上就是一XML文件,既然.Resx文件可以作为资源文件,我们肯定可以将资源定义在我们自定义的XML文件中。由于仅仅是作为演示,我尽可能简化这个XML的结构,并且仅仅提供纯文本资源内容的支持。我们自定义XML资源文件具有如下的结构:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <resources>
   3:   <add name="Greeting4Chris" value="Merry Christmas!" />
   4:   <add name="Greeting4NewYear" value="Happy Chinese New Year!" />
   5: </resources>

上篇》中讲到,.NET的资源体系包含4个重要的对象,它们分别是ResourceManager、ResourceSet、ResourceReader和ResourceWriter。要实现将自定义结构的XML作为资源文件,我们需要自定义这四个类型。

三、为XML资源存储形式定义ResourceReader和ResourceWriter

我定义了如下一个XmlResourceReader作为读取XML资源文件的ResourceWriter。XmlResourceReader实现接口IResourceReader,在构造函数中将资源内容从XML文件中读取出来保存在一个XmlDocument对象中。在GetEnumerator方法中将该XmlDocument得内容转换成一个Hashtable,并返回该Hashtable的Enumerator。

   1: public class XmlResourceReader: IResourceReader
   2: {
   3:     public XmlDocument Document { get; private set; }
   4:     public XmlResourceReader(string fileName)
   5:     {
   6:         this.Document = new XmlDocument();
   7:         this.Document.Load(fileName);
   8:     }
   9:     public XmlResourceReader(Stream stream)
  10:     {
  11:         this.Document = new XmlDocument();
  12:         this.Document.Load(stream);
  13:     }
  14:     public IDictionaryEnumerator GetEnumerator()
  15:     {
  16:         Dictionary<string, string> set = new Dictionary<string, string>();
  17:         foreach (XmlNode item in this.Document.GetElementsByTagName("add"))
  18:         {
  19:             set.Add(item.Attributes["name"].Value, item.Attributes["value"].Value);
  20:         }
  21:         return set.GetEnumerator();
  22:     }
  23:     IEnumerator IEnumerable.GetEnumerator()
  24:     {
  25:         return GetEnumerator();
  26:     }
  27:     public void Dispose(){}
  28:     public void Close(){}
  29: }

将资源内容写入XML文件的实现定义在如下一个名为XmlResourceWriter的文件中,它实现接口IResourceWriter。上面说过我们的XML仅仅提供对于纯文本内容的支持,在这里我们仅仅实现了value参数类型为string的AddResource方法。XmlResourceWriter的逻辑很简单,仅仅涉及到对于XmlDocument节点的添加和保存,所以在这里无需再多作介绍了。

   1: public class XmlResourceWriter: IResourceWriter
   2: {
   3:     public XmlDocument Document { get; private set; }
   4:     private string fileName;
   5:     private XmlElement root;
   6:  
   7:     public XmlResourceWriter(string fileName)
   8:     {           
   9:         this.fileName = fileName;
  10:         this.Document = new XmlDocument();
  11:         this.Document.AppendChild(this.Document.CreateXmlDeclaration("1.0", "utf-8",null));
  12:         this.root = this.Document.CreateElement("resources");
  13:         this.Document.AppendChild(this.root);
  14:     }
  15:  
  16:     public void AddResource(string name, byte[] value)
  17:     {
  18:         throw new NotImplementedException();
  19:     }
  20:  
  21:     public void AddResource(string name, object value)
  22:     {
  23:         throw new NotImplementedException();
  24:     }
  25:  
  26:     public void AddResource(string name, string value)
  27:     {
  28:         var node = this.Document.CreateElement("add");
  29:         node.SetAttribute("name", name);
  30:         node.SetAttribute("value", value);
  31:         this.root.AppendChild(node);
  32:     }
  33:  
  34:     public void Generate()
  35:     {
  36:         using (XmlWriter writer = new XmlTextWriter(this.fileName, Encoding.UTF8))
  37:         {
  38:             this.Document.WriteTo(writer);
  39:         }
  40:     }
  41:     public void Dispose(){}
  42:     public void Close() { }
  43: }

 

四、为XML资源存储形式定义ResourceSet

ResourceReader和ResourceWriter已经创建完毕,现在我们来创建自定义的ResourceSet:XmlResorceSet。我们定义的XmlResourceReader在构造函数中被实例化,在ReadResource方法执行过程中,它将被用于完成资源内容的读取操作,读取的结果最终用于初始化该XmlResuorceSet对象。

   1: public class XmlResourceSet : ResourceSet
   2: {
   3:     public XmlResourceSet(Stream stream)
   4:     {
   5:         this.Reader = new XmlResourceReader(stream);
   6:         this.Table = new Hashtable();
   7:         this.ReadResources();
   8:     }
   9:     public XmlResourceSet(string fileName)
  10:     {
  11:         base.Reader = new XmlResourceReader(fileName);
  12:         base.Table = new Hashtable();
  13:         this.ReadResources();
  14:     }
  15:     public override Type GetDefaultReader()
  16:     {
  17:         return typeof(XmlResourceReader);
  18:     }
  19:     public override Type GetDefaultWriter()
  20:     {
  21:         return typeof(XmlResourceWriter);
  22:     }
  23: }

 

五、为XML资源存储形式定义ResourceManager

最后一部自然是创建我们自定义的ResourceManager:XmlResourceManager。和之前创建的BinaryResourceManager、ResXResourceManager一样,我们只需要重写InternalGetResourceSet方法,返回相应的ResourceSet对象即可,在这里返回的自然是上面创建的XmlResourceSet。

   1: public class XmlResourceManager: FileResourceManager
   2: {
   3:     public XmlResourceManager(string directory, string baseName)
   4:         : base(directory, baseName, ".xml")
   5:     {}
   6:  
   7:     protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
   8:     {
   9:         return new XmlResourceSet(this.GetResourceFileName(culture));
  10:     }
  11: }

将XmlResourceManager放进我们的演示程序,你依然可以得到一样的结果

   1: PrepareFiles("GreetingMessages", "xml");
   2:  
   3: AddResource(() => new XmlResourceWriter("GreetingMessages.xml"), new CultureInfo("en-US"));
   4: AddResource(() => new XmlResourceWriter("GreetingMessages.en-US.xml"), new CultureInfo("en-US"));
   5: AddResource(() => new XmlResourceWriter("GreetingMessages.zh-CN.xml"), new CultureInfo("zh-CN"));
   6:  
   7: DisplayResource(new XmlResourceManager("", "GreetingMessages"));

执行结果

   1: English (United States)
   2:         Merry Christmas!
   3:         Happy Chinese New Year!
   4:  
   5: Chinese (Simplified, PRC)
   6:         圣诞快乐!
   7:         新年快乐!
   8:  
   9: Japanese (Japan)
  10:         Merry Christmas!
  11:         Happy Chinese New Year!

 

六、补充

XmlResourceManager的定义仅仅为你提供了一种实现自定义资源存储形式的解决方案,按照一样的思路,你可以采用其他的资源存储形式,比较有价值的应该是将资源内容定义在数据库表中。在分布式架构中,你甚至可以通过远程调用服务的方式来获取资源,不过在这种情况下,你应该考虑进行相应的缓存机制提升性能。

posted on 2012-01-21 22:45  Arrow.Lu  阅读(157)  评论(0)    收藏  举报