<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对应的参数值。然后进行返回使用。