有些同学可能没有找到源码,只是看jar包中的class文件或者反编译出来看,这里放一个链接mybatis源码可以下载下来看,比较方便。
这篇记录下配置文件的中的properties节点的使用。
一般在配置文件中使用如下:
<properties resource="jdbc.properties">
<property name="username" value="root" />
<property name="password" value="123" />
</properties>
或者配合environment节点下的datasource使用
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
那么我们来看下,这个properties节点是如何解析的。
首先还是来到我们熟悉的类XMLConfigBuilder.java
parseConfiguration()解析各个节点
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
//解析properties节点
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
那么在解析properties节点的时候顺序是什么样,如果我们既配置读取外部文件又在xml中进行配置,最终会用哪个呢?
在propertiesElement()方法中我们可以看到:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//先解析xml中Property几点
Properties defaults = context.getChildrenAsProperties();
//在解析外部文件
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//resource和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.");
}
//把外部文件放入defaults中,意味着如果与xml中的name同名,将会被外部文件的值覆盖
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.setVariables(defaults);
}
}
有此我们看到,有限读取xml中property的节点值,但是如果外部配置文件中有相同的名字,这个值将会被覆盖。
在解析property的节点解析如下:
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
//获取子节点
for (XNode child : getChildren()) {
//获取name
String name = child.getStringAttribute("name");
//获取value
String value = child.getStringAttribute("value");
if (name != null && value != null) {
//放入map中
properties.setProperty(name, value);
}
}
return properties;
}
对于我们经常用到占位符${},mybatis是如何解析的呢?
在解析node的方法中,
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;
}
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
//传入"${"和"}"开始结束符
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
public String parse(String text) {
final StringBuilder builder = new StringBuilder();
final StringBuilder expression = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
expression.setLength(0);
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}
public String handleToken(String content) {
//获取占位符内字段和配置文件相同的字段进行替换
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
在这个方法中进行解析和赋值。