我觉得 Xml 作为配置文件最大的好处是结构化,对程序配置信息可以有结构有组织的进行描述。但是使用 Xml 作为配置文件带给我们的问题是我们需要程序自己去解析Xml配置文件结构。这显得有点多余且啰嗦。
因此通常我们的配置文件都使用 java 提供的属性文件,属性文件虽然没有 Xml 文件那样清晰的结构化,但只要标注好注释属性文件还是可以胜任这份工作的。
Hasor 使用 Xml 作为其配置文件,但是可以像属性文件一样去访问它。
首先 Hasor 会将 Xml 片段映射成一组 Map 的 key/value 键值对,如下:
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://project.hasor.net/hasor/schema/main"> <!-- Demo 项目源码所处包 --> <hasor debug="false"> <loadPackages>net.test.project.*</loadPackages> </hasor> </config>
hasor = <...> hasor.debug = <...> hasor.loadPackages = <...>
大家可能有点摸不清头脑是怎样一种规则,下面我就来介绍一下 Hasor 配置文件的映射规则。
以 XPath 为例,当我们需要访问"<hasor>"节点时候,可以使用“hasor”这个表达式。如果需要获取"<loadPackages>"节点的话使用“hasor/loadPackage”这个表达式。
Hasor 的规则与其相似。Hasor 在建立节点映射关系时会将其 XPath 作为生成 key 的依据。不同于 XPath 的是 hasor 使用“.”分割。例如:loadPackage节点的映射关系是“hasor.loadPackage”。
如果要想取得 Xml 配置文件中 debug 这个属性的值,通常 Xpath 的表达式为“hasor/@debug”,在 hasor 中属性的映射会变为:“hasor.debug”。或许有的同学会觉得如果有一个名为“debug”的 xml 元素位于 hasor 元素下岂不是会出现冲突?
没错在 Hasor 的映射规则下会出现冲突,而且只会有一个有效。Hasor 设计统一配置文件读取策略的目的并不是为了取代 XPath,而是提供一种便捷的 Xml 配置文件获取方式。
举一个比较复杂的例子的映射关系,下面这段 Xml 取自Demo程序的配置文件,它配置了Demo程序使用的数据库连接信息:
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://project.hasor.net/hasor/schema/main"> <!-- 数据源配置 --> <hasor-jdbc> <dataSourceSet default="localDB"> <!-- 名称为 localDB 的内存数据库,数据库引擎使用 Hsql --> <dataSource name="localDB" dsFactory="net.hasor.plugins.datasource.factory.C3p0Factory"> <driver>org.hsqldb.jdbcDriver</driver> <url>jdbc:hsqldb:mem:aname</url> <user>sa</user> <password></password> </dataSource> </dataSourceSet> </hasor-jdbc> </config>
对于上面这段 Xml 如果我要取得数据库连接的用户名"sa",需要使用这个映射 key:“hasor-jdbc . dataSourceSet . dataSource . user”,可以看到取得某个元素的值其实就是通过元素名一路“.”下去的。
如果我要取得的是dsFactory 属性信息,可以使用这个映射 key:“hasor-jdbc . dataSourceSet . dataSource . dsFactory”。
大家可以看出,其实 Hasor 在映射时 xml 属性会被认为是元素的子元素,换句话说,上面这段配置文件可以改为下面这种写法:
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://project.hasor.net/hasor/schema/main"> <hasor-jdbc> <dataSourceSet> <default>localDB</default> <dataSource> <name>localDB</name> <dsFactory>net.hasor.plugins.datasource.factory.C3p0Factory</dsFactory> <driver>org.hsqldb.jdbcDriver</driver> <url>jdbc:hsqldb:mem:aname</url> <user>sa</user> <password></password> </dataSource> </dataSourceSet> </hasor-jdbc> </config>
1.性能
上面介绍了 Hasor 的配置文件映射规则,下面解释一下 Hasor 配置文件读取机制。
初一看可能有的同学会觉得,Hasor 在读取配置文件时或许采用了 XPath 表达式进行读取。如果遇到频繁访问时性能会极具下降。
我可以告诉大家,Hasor 考虑到 XPath 性能的问题,因此它内部并不是通过 XPath 进行解析,而是使用 Map进行映射。
Hasor 在启动时会调用 Sax 方式扫描整个 Xml 文件,然后在扫描过程中持续的生成Key/Value 键值对,这些键值对最后会保存到一个 Map 中,我们取得的所有配置文件信息最后都会在这个Map中去查找。
正因为这种方式 Hasor 的配置文件读取速度不会输给属性文件。
2.覆盖问题
由于 Hasor 的映射规则 Hasor 的配置文件在生成最终 Map 时会产生 key 覆盖问题。前面已经提到了一种覆盖现象,属性名和子元素名同名情况下就会出现覆盖。
此外同名子元素也会存在 key 覆盖问题,这些问题是 hasor 映射规则无法解决的。并且 Hasor 的映射规则也不会力争去解决这个问题,因为 Hasor 的配置映射规则并不打算成为另外一个 XPath。
覆盖问题虽然会出现,但是 Hasor 却提供了一种办法解决它。
3.DOM 方式读取 Xml
即使 Hasor 通过 Map 方式建立的映射,并且也是通过 Map 方式获取节点。但是这并不能阻拦 Haso 为您提供一套 DOM 方式获取内容的途径。
各位或许觉得通过 DOM 方式会引发 Hasor 重载配置文件,或者说 Hasor 内部除了 Map 映射还保留了另外一个 DOM树。那么您就大错特错了!
Hasor 存储 Xml 映射信息只有 Map 这一种途径,Hasor 不会浪费多余的内存空间。Hasor 在保存映射时它为每一个 Xml 映射节点都使用 XmlNode 接口进行封装。
大到一个 Xml 元素,小到一个 Xml 属性。凡是 Xml 中可到达的元素都是通过这个对象进行封装。
如果 XmlNode 表示的是一个元素,那么这个对象的 children 属性中就保存了它的所有子节点,而 arrMap 属性保存的就是这个元素的所有属性。因此我们只要通过映射 key 取得 XmlNode 接口即可。
这样一来就可以在不增加内存开销的情况下增加 DOM 方式读取配置文件。
例如下面这个 Xml 例子:
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://project.hasor.net/hasor/schema/main"> <demoProject> <menus> <menu code="FunA" name="功能演示A" url="/funa" /> <menu code="FunB" name="功能演示B" url="/funb" /> <menu code="UserMgr" name="用户管理" url="/mgr/user/userList.do" /> </menus> </demoProject> </config>
解析上面这段 Xml 将它的 menu 元素信息保存到List 中:
List<MenuBean> menuList = new ArrayList<MenuBean>(); /*获取操纵配置文件的接口*/ Settings setting = appContext.getSettings(); /*取得‘/demoProject/menus’ Xml节点*/ XmlNode xmlNode = setting.getXmlProperty("demoProject.menus"); /*使用 DOM 方式解析 Xml节点*/ List<XmlNode> menus = xmlNode.getChildren("menu"); for (XmlNode node : menus) { MenuBean menuBean = new MenuBean(); menuBean.setCode(node.getAttribute("code")); menuBean.setName(node.getAttribute("name")); menuBean.setUrl(node.getAttribute("url")); menuList.add(menuBean); }
4.关于根节点
Hasor 的配置文件默认是不映射根节点的,如果开发者需要获取根节点则需要通过映射 key“.”取得。当配置文件中存在多个命名空间定义情况下,根节点也会出现多个。换句话说根节点是针对 Xml 命名空间的而非 Xml 文件内容本身。
5.命名空间
Hasor 要求被解析的 Xml 文件必须有一个命名空间,命名空间地址可以是任意的。这就意味着您可以同时装载两个不同命名空间下的 Xml 配置文件,并且随意读取它们。Hasor 目前已经在使用的命名空间如下:
- 主配置文件:http://project.hasor.net/hasor/schema/main
- Hasor-Core:http://project.hasor.net/hasor/schema/hasor-core
- Hasor-Web:http://project.hasor.net/hasor/schema/hasor-web
- Hasor-JDBC:http://project.hasor.net/hasor/schema/hasor-jdbc
Hasor 的 Xml 解析支持多个命名空间会给程序带来很大的意义。比方说我们为每个不同的业务模块都设立一个命名空间,然后不同业务模块的开发交给不同的开发小组完成。最后在合并这些开发小组所产生的配置文件。
通常我们会统一维护配置文件,谁修改了配置文件他就马上递交。后来者在修改时需要及时更新一下看看是否有更改。当开发小组成员比较集中的时候,这种办法勉强是不会出现问题的。
如果开发小组的成员不经常在一起,这种统一式的管理变得不那么靠谱。尤其在异地开发模式中显得更加突出。即使是通过属性文件维护程序的配置文件也会面对这个问题。
在这种场景下,一般我们可能会写好多个属性配置文件。然后在分别用不同的 map 载入它们统一管理。虽然可以解决问题,但是我觉得这并不优雅。
Hasor 的配置文件可以通过命名空间对业务模块加以区分,不同的业务模块当使用配置文件时从自己的命名空间中取值,不影响其它模块。这样一来既能可以很好控制冲突的发生,又能统一维护配置文件。
通常一般情况下一个模块通常也是由一个小组来完成,这样更加可以确保冲突的发生概率。重而很好的满足 分布式开发 的需求。
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:mod1="http://mode1.myProject.net" xmlns:mod2="http://mode2.myProject.net" xmlns="http://project.hasor.net/hasor/schema/main"> <!-- mode1 配置 --> <mod1:config> <mod1:appSettings> <mod1:serverLocal mod1:url="www.126.com" /> </mod1:appSettings> </mod1:config> <!-- mode2 配置 --> <mod2:config> <mod2:appSettings> <mod2:serverLocal mod2:url="www.souhu.com" /> </mod2:appSettings> </mod2:config> </config>
在这个配置文件中其实是定义了三个命名空间,之所以这样写的目的是为了看起来更加直观。下面是测试代码:
File inFile = new File("ns-config.xml"); FileSettings settings = new FileSettings(inFile); // //Mode1 Settings mod1 = settings.getSetting("http://mode1.myProject.net"); Settings mod2 = settings.getSetting("http://mode2.myProject.net"); System.out.println(mod1.getString("appSettings.serverLocal.url")); System.out.println(mod2.getString("appSettings.serverLocal.url"));
在多个命名空间下 Hasor 的配置文件解析会为每个命名空间的元素都建立一个根节点。
6.多配置文件同时加载
为了更加方便协作开发,Hasor 的 Xml 配置文件解析机制还支持多个配置文件同时加载。
加载的不同配置中文件会出现冲突问题,Hasor 当遇到冲突时采用的 内容覆盖&合并 机制。
例如:如果遇到相同映射 key 的元素,Hasor 会使用新的元素内容覆盖已存在的元素内容。新元素的子元素和属性会按照顺序追加到已有老元素的 children 和 attrMap 中。
但是这种合并不会将两个不同命名空间下的冲突元素合并到一起。它们会被保存在彼此隔离的两个不同 Map 中。这个 Map 是在映射 key/value 键值对的上层建立的一个命名空间 Map。这个结构是Map<String,Map<String,String>> 结构。
所以说如果加载的两个配置文件虽然有相同的属性映射 key ,但处于不同命名空间下。Hasor 的配置文件加载不会认为它们是冲突的,您使用上面的代码仍然可以读取到位于两个配置文件中的不同配置。例如:
------ns1-config.xml <?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://mode1.myProject.net"> <appSettings> <serverLocal url="www.126.com" /> </appSettings> </config> ------ns2-config.xml <?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://mode2.myProject.net"> <appSettings> <serverLocal url="www.souhu.com" /> </appSettings> </config>
程序代码如下:
File inFile1 = new File("ns1-config.xml"); File inFile2 = new File("ns2-config.xml"); FileSettings settings = new FileSettings(); settings.addFile(inFile1); settings.addFile(inFile2); settings.refresh(); // Settings mod1 = settings.getSetting("http://mode1.myProject.net"); Settings mod2 = settings.getSetting("http://mode2.myProject.net"); System.out.println(mod1.getString("appSettings.serverLocal.url")); System.out.println(mod2.getString("appSettings.serverLocal.url"));
7.“static-config.xml”和“hasor-config.xml”
Hasor 的配置文件按照作用分为两种,它们分别用于不同的场景。static-config.xml 配置文件通常是伴随hasor软件包一同发布的。您可以在:Hasor-Core、Hasor-Web、Hasor-JDBC 它们的 jar 包中找到它们的身影。
“static-config.xml”顾名思义静态配置,意思就是说这些配置文件内容是不可以被改变的。且文件名也是固定的,这也包括了它们的存放位置。
通常静态配置文件中保存的是默认配置,在软件开发中一般不会涉及到编写一个“static-config.xml”。当然如果您的软件中有很多配置信息不想暴露给实施人员,可以考虑将这些配置项目放到static-config.xml中。
Hasor 在启动时候会先加载位于 classpath 中所有指定目录中的 static-config.xml,并且按照前面说的逻辑进行处理。static-config.xml 的加载顺序取决于 jar 包扫描顺序,由于具体环境的复杂性,您可以认为它们的加载顺序是不可靠的。
在接触 Hasor 的配置文件时,经常被提及的是 hasor-config.xml 。它被成为主配置文件,作为主配置文件它的内容是最高优先级的。它的加载是在 static-config.xml 加载完成之后,这样可以确保 hasor-config.xml 的内容覆盖默认配置。
此外,Hasor 的配置文件系统还会监控主配置文件是否在运行期发生了改变。如果发生改变,Hasor 会重载这个配置文件,然后通过SettingsListener 接口通知配置文件重载了。
开发者可以使用 SettingsListener 接口开发出动态修改配置文件内容而让程序自动感知的效果,Hasor-Core 中绝大部分配置都是支持 及时修改及时生效的。而 Hasor-Web 和 Hasor-JDBC 中的配置却不支持及时修改及时生效。
8.工具类及层次结构
图中蓝色部分表示接口,灰色部分表示类。Hasor 的整个配置文件服务类的层次关系如上图所示。
AbstractSettings 该类主要的目的是抽象出 key/value 数据容器(Map)。并基于这个容器实现大部分 Settings 接口中的方法。
AbstractBaseSettings 抽象类是基于 AbstractSettings 的功能对数据容器提供了命名空间区分支持,该类实现了getSetting(namespace) 接口方法和getSettingArray 接口方法。
InputStreamSettings 类是所有基于流形式加载配置文件的基类,该类实现了 IOSettings 接口。并且通过 loadSettings 方法实现配置文件加载。也是该类提供了基于 Sax 下的 Xml 配置文件解析支持,这部分代码位于SaxXmlParser 类中。该类还支持多个 InputStream 的顺序解析。
FileSettings 是一个独立的 Hasor 配置文件解析服务类,第六小节已经展示了如何使用它。
StandardContextSettings 该类是 Hasor 配置文件体系所使用的工具类,该类提供了“static-config.xml”和“hasor-config.xml”配置文件的加载支持。
第7小节所阐述的配置文件修改监听器功能,是由Environment 部分提供的,本文暂不提及。
-------------------------------
以上就是 Hasor 配置文件解析的主要内容,由于篇幅的关系。有关扩展部分留在以后在介绍。
在最后,很多同学看完之后可能觉得 Hasor 的配置文件体系,似乎有点超越配置文件本身所做的事情。的确是这样,因为 Hasor 需要为开发者提供基于 Xml 下统一的最简化的配置文件解析服务。因此很多方面都需要考虑周全,因此才有的这个规模。
最后奉献上 Hasor 配置文件接口的定义供大家欣赏,最后祝大家工作愉快,元旦快乐。
public interface Settings { //在框架扫描包的范围内查找具有特征类集合。(特征可以是继承的类、标记某个注解的类) public Set<Class<?>> getClassSet(Class<?> featureType,String[] loadPackages); //在框架扫描包的范围内查找具有特征类集合。(特征可以是继承的类、标记某个注解的类) public Set<Class<?>> getClassSet(Class<?> featureType,String loadPackages); //获取指在某个特定命名空间下的Settings接口对象。 public String[] getSettingArray(); //获取指在某个特定命名空间下的Settings接口对象。 public Settings getSetting(String namespace); //强制重新装载配置文件。 public void refresh() throws IOException; // //解析全局配置参数,并且返回其{@link Character}形式对象。 public Character getChar(String name); //解析全局配置参数,并且返回其{@link Character}形式对象。第二个参数为默认值。 public Character getChar(String name,Character defaultValue); //解析全局配置参数,并且返回其{@link String}形式对象。 public String getString(String name); //解析全局配置参数,并且返回其{@link String}形式对象。第二个参数为默认值。 public String getString(String name,String defaultValue); //解析全局配置参数,并且返回其{@link Boolean}形式对象。 public Boolean getBoolean(String name); //解析全局配置参数,并且返回其{@link Boolean}形式对象。第二个参数为默认值。 public Boolean getBoolean(String name,Boolean defaultValue); //解析全局配置参数,并且返回其{@link Short}形式对象。 public Short getShort(String name); //解析全局配置参数,并且返回其{@link Short}形式对象。第二个参数为默认值。 public Short getShort(String name,Short defaultValue); //解析全局配置参数,并且返回其{@link Integer}形式对象。 public Integer getInteger(String name); //解析全局配置参数,并且返回其{@link Integer}形式对象。第二个参数为默认值。 public Integer getInteger(String name,Integer defaultValue); //解析全局配置参数,并且返回其{@link Long}形式对象。 public Long getLong(String name); //解析全局配置参数,并且返回其{@link Long}形式对象。第二个参数为默认值。 public Long getLong(String name,Long defaultValue); //解析全局配置参数,并且返回其{@link Float}形式对象。 public Float getFloat(String name); //解析全局配置参数,并且返回其{@link Float}形式对象。第二个参数为默认值。 public Float getFloat(String name,Float defaultValue); //解析全局配置参数,并且返回其{@link Double}形式对象。 public Double getDouble(String name); //解析全局配置参数,并且返回其{@link Double}形式对象。第二个参数为默认值。 public Double getDouble(String name,Double defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。 public Date getDate(String name); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date getDate(String name,Date defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date getDate(String name,long defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。 public Date getDate(String name,String format); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date getDate(String name,String format,long defaultValue); //解析全局配置参数,并且返回其{@link Enum}形式对象。第二个参数为默认值。 public <T extends Enum<?>> T getEnum(String name,Class<T> enmType); //解析全局配置参数,并且返回其{@link Enum}形式对象。第二个参数为默认值。 public <T extends Enum<?>> T getEnum(String name,Class<T> enmType,T defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象(用于表示文件)。第二个参数为默认值。 public String getFilePath(String name); //解析全局配置参数,并且返回其{@link Date}形式对象(用于表示文件)。第二个参数为默认值。 public String getFilePath(String name,String defaultValue); //解析全局配置参数,并且返回其{@link File}形式对象(用于表示目录)。第二个参数为默认值。 public String getDirectoryPath(String name); //解析全局配置参数,并且返回其{@link File}形式对象(用于表示目录)。第二个参数为默认值。 public String getDirectoryPath(String name,String defaultValue); //解析全局配置参数,并且返回其{@link XmlNode}形式对象。 public XmlNode getXmlProperty(String name); // //解析全局配置参数,并且返回其{@link Character}形式对象。 public Character[] getCharArray(String name); //解析全局配置参数,并且返回其{@link Character}形式对象。第二个参数为默认值。 public Character[] getCharArray(String name,Character defaultValue); //解析全局配置参数,并且返回其{@link String}形式对象。 public String[] getStringArray(String name); //解析全局配置参数,并且返回其{@link String}形式对象。第二个参数为默认值。 public String[] getStringArray(String name,String defaultValue); //解析全局配置参数,并且返回其{@link Boolean}形式对象。 public Boolean[] getBooleanArray(String name); //解析全局配置参数,并且返回其{@link Boolean}形式对象。第二个参数为默认值。 public Boolean[] getBooleanArray(String name,Boolean defaultValue); //解析全局配置参数,并且返回其{@link Short}形式对象。 public Short[] getShortArray(String name); //解析全局配置参数,并且返回其{@link Short}形式对象。第二个参数为默认值。 public Short[] getShortArray(String name,Short defaultValue); //解析全局配置参数,并且返回其{@link Integer}形式对象。 public Integer[] getIntegerArray(String name); //解析全局配置参数,并且返回其{@link Integer}形式对象。第二个参数为默认值。 public Integer[] getIntegerArray(String name,Integer defaultValue); //解析全局配置参数,并且返回其{@link Long}形式对象。 public Long[] getLongArray(String name); //解析全局配置参数,并且返回其{@link Long}形式对象。第二个参数为默认值。 public Long[] getLongArray(String name,Long defaultValue); //解析全局配置参数,并且返回其{@link Float}形式对象。 public Float[] getFloatArray(String name); //解析全局配置参数,并且返回其{@link Float}形式对象。第二个参数为默认值。 public Float[] getFloatArray(String name,Float defaultValue); //解析全局配置参数,并且返回其{@link Double}形式对象。 public Double[] getDoubleArray(String name); //解析全局配置参数,并且返回其{@link Double}形式对象。第二个参数为默认值。 public Double[] getDoubleArray(String name,Double defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。 public Date[] getDateArray(String name); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date[] getDateArray(String name,Date defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date[] getDateArray(String name,long defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象。 public Date[] getDateArray(String name,String format); //解析全局配置参数,并且返回其{@link Date}形式对象。第二个参数为默认值。 public Date[] getDateArray(String name,long defaultValue); //解析全局配置参数,并且返回其{@link Enum}形式对象。第二个参数为默认值。 public <T extends Enum<?>> T[] getEnumArray(String name,Class<T> enmType); //解析全局配置参数,并且返回其{@link Enum}形式对象。第二个参数为默认值。 public <T extends Enum<?>> T[] getEnumArray(String name,T defaultValue); //解析全局配置参数,并且返回其{@link Date}形式对象(用于表示文件)。第二个参数为默认值。 public String[] getFilePathArray(String name); //解析全局配置参数,并且返回其{@link Date}形式对象(用于表示文件)。第二个参数为默认值。 public String[] getFilePathArray(String name,String defaultValue); //解析全局配置参数,并且返回其{@link File}形式对象(用于表示目录)。第二个参数为默认值。 public String[] getDirectoryPathArray(String name); //解析全局配置参数,并且返回其{@link File}形式对象(用于表示目录)。第二个参数为默认值。 public String[] getDirectoryPathArray(String name,String defaultValue); //解析全局配置参数,并且返回其{@link XmlNode}形式对象。 public XmlNode[] getXmlPropertyArray(String name); }