<properties>标签应用
该部分主要分析<properties>标签的应用过程,其中主要讲解PropertyParser在这个过程中扮演的角色和作用。
1、场景设定
该场景主要是使用<properties>标签定义数据库连接参数,然后在数据源配置中使用<properties>标签中定义的参数,其中并开启了占位符的默认配置。
第一部分:
<properties>
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/>
<property name="username" value="root"/>
<!---<property name="password" value="123456"/> -->
</properties>
第二部分:
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<!-- 配置数据库连接信息 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password?:123456}" />
</dataSource>
</environment>
</environments>
2、<properties>标签的加载过程
第一步,从XMLConfigBuilder类的parseConfiguration()方法开始进行分析。代码如下(其他代码省略):
private void parseConfiguration(XNode root) {
try {
……
……
//解析配置文件中的properties元素
propertiesElement(root.evalNode("properties"));
……
……
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
第二步:propertiesElement()方法是解析<properties>标签的入口,在调用该方法时,传入的参数是<properties>标签对应的XNode实例对象。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {//resource 和 url 只能够配置其中一个,否则抛出异常。
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//properties元素体内指定的属性会被配置文件中同名属性覆盖
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
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()) {
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) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName();
this.variables = variables;
this.attributes = parseAttributes(node);
this.body = parseBody(node);
}
上述,第1部分代码,第3行代码中XNode.getChildrenAsProperties()方法,是用于解析<properties>标签子元素<property>的,并把解析结果转换成Properties形式参数,最终的值被存储到了configuration实例对象的variables字段中,具体实现如第2部分代码所示,其中第3行代码中getChildren()方法,用来获取<properties>标签对应的XNode对象的所有子元素,即所有<property>标签对应的XNode对象;getChildren()方法的具体实现,如第3部分代码所示,其中第8行代码用来构建<property>标签对应的XNode对象,具体代码实现如第4部分代码所示。在第4部分代码中,第6行代码是用来解析<property>标签中的属性,并存到对象的attributes 属性字段中,具体实现如下:
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
在上述代码片段中,我们就看见了梦寐以求的主角——PropertyParser类了。从现在开始就进入了PropertyParser的世界中来了,首先以下面代码为例:
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
这个时候在第一次调用PropertyParser.parse()方法时,第一个参数就是”org.apache.ibatis.parsing.PropertyParser.enable-default-value”了。在调用parse()方法时,因为参数中没有含有开始标签,所以根据前面讲到的GenericTokenParser.parse()方法,就直接原样返回了。
通过前面的分析,我们可以了解到,<properties>标签中的子元素,通过一系列的解析处理,最终是key-value的Properties形式,存储到了configuration实例对象的variables变量中,等待后续的使用。
第三步:在占位符中如何使用上述定义的参数。首先,还是从XMLConfigBuilder类的parseConfiguration()开始,然后进入environmentsElement()方法中,再进入dataSourceElement()方法中,该方法的参数即为<dataSource>标签对应XNode实例对象。dataSourceElement()方法代码如下:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
在上述代码中,第4行,即XNode.getChildrenAsProperties()方法,是用于解析当前标签下子标签<property>的,并把解析结果转换成Properties形式参数。后续过程和上面分析的<properties>标签一样,不再重复,其中,主要区别在于下面代码中的value的值(<properties>标签中是常量,本处是占位符):
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password?:123456}" />
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
因为在解析<dataSource>标签下的子标签<property>时,其中的value值对应的是占位符,所以在上述第7行代码中,PropertyParser.parse(attribute.getNodeValue(), variables)方法传递的第一个参数是”
x
x
x
"
格
式
的
,
且
v
a
r
i
a
b
l
e
s
的
值
即
为
c
o
n
f
i
g
u
r
a
t
i
o
n
实
例
对
象
的
v
a
r
i
a
b
l
e
s
字
段
值
。
在
P
r
o
p
e
r
t
y
P
a
r
s
e
r
.
p
a
r
s
e
(
)
中
,
首
先
定
义
了
V
a
r
i
a
b
l
e
T
o
k
e
n
H
a
n
d
l
e
r
实
例
对
象
,
然
后
又
定
义
了
G
e
n
e
r
i
c
T
o
k
e
n
P
a
r
s
e
r
实
例
对
象
,
最
后
执
行
G
e
n
e
r
i
c
T
o
k
e
n
P
a
r
s
e
r
实
例
对
象
的
p
a
r
s
e
(
)
方
法
。
具
体
过
程
如
下
:
在
G
e
n
e
r
i
c
T
o
k
e
n
P
a
r
s
e
r
实
例
对
象
的
p
a
r
s
e
(
)
方
法
处
理
"
{xxx}"格式的,且variables的值即为configuration实例对象的variables字段值。在PropertyParser.parse()中,首先定义了VariableTokenHandler实例对象,然后又定义了GenericTokenParser实例对象,最后执行GenericTokenParser实例对象的parse()方法。具体过程如下:在GenericTokenParser实例对象的parse()方法处理"
xxx“格式的,且variables的值即为configuration实例对象的variables字段值。在PropertyParser.parse()中,首先定义了VariableTokenHandler实例对象,然后又定义了GenericTokenParser实例对象,最后执行GenericTokenParser实例对象的parse()方法。具体过程如下:在GenericTokenParser实例对象的parse()方法处理“{xxx}“格式的参数。根据前面分析该方法的作用,最终是把”${xxx}“格式的参数解析成了”xxx”,并赋值给了expression变量。然后在其中调用了VariableTokenHandler实例对象的handleToken()方法,并把expression变量的值作为参数进行传递。最后,在handleToken()方法中,根据expression变量的值,即变量key,再从variables变量中取出key对应的参数值。然后进行返回使用。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68918.html