节点的解析工作由 propertiesElement 这个方法完成的

1
2
3
4
<properties resource="jdbc.properties">
<property name="jdbc.username" value="coolblog"/>
<property name="hello" value="world"/>
</properties>

我们参照上面的配置,来分析 propertiesElement 方法的逻辑。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// -☆- XMLConfigBuilder
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
Properties defaults = context.getChildrenAsProperties();
// 获取 propertis 节点中的 resource 和 url 属性值
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");

// 两者都不用空,则抛出异常
if (resource != null && url != null) {
throw new BuilderException("...");
}

if (resource != null) {
// 从文件系统中加载并解析属性文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 通过 url 加载并解析属性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 将属性值设置到 configuration 中
configuration.setVariables(defaults);
}
}

public Properties getChildrenAsProperties() {
Properties properties = new Properties();
// 获取并遍历子节点
for (XNode child : getChildren()) {
// 获取 property 节点的 name 和 value 属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");

if (name != null && value != null) {
// 设置属性到属性对象中
properties.setProperty(name, value);
}
}
return properties;
}

public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
// 获取子节点列表
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);

if (node.getNodeType() == Node.ELEMENT_NODE) {
// 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}

上面就是节点解析过程,主要包含三个步骤:

  1. 解析节点的子节点,并将解析结果设置到 Properties 对象中
  2. 从文件系统或通过网络读取属性配置,这取决于节点的 resource 和 url 是否为空。
  3. 将包含属性信息的 Properties 对象设置到XPathParser 和 Configuration 中

需要注意的是,propertiesElement方法是先解析节点的子节点内容,然后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。

这会导致同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性和属性值会覆盖掉子节点中同名的属性和及值。

配置jdbc.properties 内容如下

1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/myblog?...
jdbc.username=root
jdbc.password=1234

合并后,结果如下:
image.png