2.3.解析 Properties 子元素 初始化属性配置
Mybatis用于解析XML
的基础环境准备好之后,我们终于可以迎来第一次真正的配置文件的解析操作了。
propertiesElement(root.evalNode("properties"));
propertiesElement
方法的作用是读取mybatis全局配置文件中的properties
元素的配置,并将其转化为Properties
对象。
properties
元素是configuration
元素中的11个子节点之一,他的DTD定义如下:
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
它允许有多个property
子节点,同时也可以配置 resource
和url
属性,但是resource
和url
属性不能同时存在, 其中property
子节点的定义如下:
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
property
元素是由name
和value
构成的,因此其可以被解析成Properties
对象。 在接下来具体的解析过程中,我们会看到Mybatis针对这三种不同的配置方式都给出了对应的解析操作。
/**
* 解析Properties标签
*
* @param context Properties节点
* @throws Exception 异常
*/
private void propertiesElement(XNode context) throws Exception {
// 用户配置了properties节点
if (context != null) {
// 获取Properties下得Property节点的name 和value值,并转换Properties属性
Properties defaults = context.getChildrenAsProperties();
// 获取引用的资源
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
// 加载*.Properties资源配置文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 加载*.Properties资源配置文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 获取用户编码传入的属性配置
Properties vars = configuration.getVariables();
if (vars != null) {
// 合并属性配置
defaults.putAll(vars);
}
// 刷新解析器中的变量引用,用于接下来的解析
parser.setVariables(defaults);
// 同步到Mybatis设置中
configuration.setVariables(defaults);
}
}
首先,获取properties
对象下所有proerty
子节点,并据此生成一个Properties
实例。
// 获取Properties下得Property节点的name 和value值,并转换Properties属性
Properties defaults = context.getChildrenAsProperties();
其中context.getChildrenAsProperties()
代码如下:
/**
* 获取指定包元素下的所有子节点,在读取其name和value属性后,转换为Properties对象。
*
*/
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
// 读取name和value属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
// 同时有name和value属性则保存
properties.setProperty(name, value);
}
}
return properties;
}
他的作用是获取当前节点下的所有元素节点,在读取其name
和value
属性后,转换为Properties
对象,其代码中getChildren()
方法的作用是筛选出有效的元素节点集合:
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<>();
// 获取所有子节点
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) {
// 筛选出子节点
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
在处理完properties
的property
子节点之后,mybatis会分别获取propertie
的resource
和url
两个属性。
mybatis先校验二者不得同时存在。
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
之后根据resource
/url
属性的值加载对应的资源文件并将其内容转换成一个Properties
对象保存到上一步获取到的Properties
对象中。
if (resource != null) {
// 加载*.Properties资源配置文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 加载*.Properties资源配置文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
然后取出Configuration
中的现有的变量(这些变量由用户编码传入),然后也保存到Properties
对象中。
// 获取用户编码传入的属性配置
Properties vars = configuration.getVariables();
if (vars != null) {
// 合并属性配置
defaults.putAll(vars);
}
其实通过上诉的加载顺序我们不难理解Mybatis的属性配置,生效优先级由高到低依次为: 启动时设置的参数->Mybatis 主配置文件中properties
的url
/resource
属性指向的配置 -> Mybatis 主配置文件中properties
的property
配置。
到这一步,所有的全局属性配置就已经全部加载完毕了,接下来就是使用这些属性了。
首先更新xpathParser
中的全局变量引用,方面接下来解析文件时的使用。
// 刷新解析器中的变量引用,用于接下来的解析
parser.setVariables(defaults);
然后把这些全局属性保存到Configuration
中,properties
元素的解析操作就完成了。
// 同步到Mybatis设置中
configuration.setVariables(defaults);
补充一下:
在上面我们在根据resource
/url
属性的值加载对应的资源文件并将其内容转换成一个Properties
对象这一步操作中,涉及到了一个Resources
对象,该对象是mybatis用来简化资源访问的一个工具类。
Resources
对象中有一个ClassLoaderWrapper
类型的属性,该属性是mybatis中定义的ClassLoader
的包装类,他内部持有多个不同的ClassLoader
实现,当用户尝试通过ClassLoaderWrapper
获取资源时,改包装类会尝试使用多个ClassLoader
来加载资源。
Resources
和ClassLoaderWrapper
的实现都相对比较简单,如果感兴趣的话,可以自己查看一下源码,这里就不再赘述了。