核心配置中${}表达式配置的解析
- 一、核心配置主体
- 二、核心配置文件中properties是如何被解析的?
- 三、${} 表达式的解析
- 四、总结
前提:
核心配置文件是被XMLConfigBuilder 对象进行解析的,configuration 对象是由它父类BaseBuider继承下来的属性。
XMLConfigBuilder 对象解析完配置文件,其信息是被封装在了configuration 对象中,
然后返回,通过SqlSessionFactoryBuilder 去通过build(configuration)方法进行构建SqlSessionFactory对象,
一个数据库是关联一个environment 的,所以是一个sqlSessionFactory 对象对应一个数据库,实际上也对应一个configuration 对象........
一、核心配置主体
配置信息的配置主体先进行阐明:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//源码中没有这一句,只有 parseConfiguration(parser.evalNode("/configuration"));
//为了让读者看得更明晰,源码拆分为以下两句
XNode configurationNode = parser.evalNode("/configuration");
parseConfiguration(configurationNode);
return configuration;
}
/**
* 解析 "/configuration"节点下的子节点信息,然后将解析的结果设置到Configuration对象中
*/
private void parseConfiguration(XNode root) {
try {
//1.首先处理properties 节点
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
//2.处理typeAliases
typeAliasesElement(root.evalNode("typeAliases"));
//3.处理插件
pluginElement(root.evalNode("plugins"));
//4.处理objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//5.objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.settings
settingsElement(root.evalNode("settings"));
//7.处理environments
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
//8.database
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.typeHandlers
typeHandlerElement(root.evalNode("typeHandlers"));
//10.mappers
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
再看核心配置文件中DTD约束,不难看出其解析顺序和约束是一致的。这些解析出来的结果最后都会封装到configuration对象中。
二、核心配置文件中properties是如何被解析的?
properties的三种配置方式:
- 通过property 子标签,进行name<==>value进行配置;
- 通过url 属性(外配置文件);
- 通过resource 属性(外配置文件)。为契合项目路径,这种方式使用的多。
下面阅读已被吾注解好了的解析properties 的代码
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 第一种配置方式
Properties defaults = context.getChildrenAsProperties();
// 第三种利用resource配置
String resource = context.getStringAttribute("resource");
// 第二种利用url进行配置
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.");
}
// 下面就是分别对resource和url进行判断了
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 这个是看原先有没有对cofiguration中的variables属性赋值
// 如果有的话一并加入到defaults这个对象中
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// parser 是一个XPathParser的对象;其中有variable是其中的一个属性
// variable 是Properties 对象;
// 先对parser 的variable的属性进行赋值,后面用于 对datasource 的配置有用
parser.setVariables(defaults);
// 解析的结果最后得在configuration中,所以....
configuration.setVariables(defaults);
}
}
这里需要注意的有两点:
- SqlSessionBuilder执行build方法的时候,也是可以传一个Properties 对象的,这个对象会在XMLConfigBuilder对象创建的时候赋值给configuration对象,这也就是上面源码那个为什么要去判断一下有没有提前赋值(上面的
vars
)。 - 这里defaults 对象虽然不允许 url 和 resource 属性值都时接受,但是允许接受完 url 或 resource的配置文件后还可以加上第一种配置产生的值,还可以接受 build 传过来的配置,非常的灵活。
Configuration
对象中的set、get方法是对外提供的,当然也可以自行对其进行修改和获取。当然没啥事谁修改这玩意啊。
三、${} 表达式的解析
回到根本,${} 到底是如何解析的呢?
首先需要了解 XNode 类中的 evalNode(String expression)
方法。
这个 XPathParser 对象都是使用的 XMLConfigBuilder 内的属性 parser 对象。
variables 解析其他标签也都是共享的,一级传一级的。
所以也就是说解析 datasource 是可以使用共享的 variables 的。
然后就可以看解析的 environmentElement 了,看看。
解析这个 '${}‘ 的核心代码如下:
/**
* 这个类解析${}这种形式的表达式
*/
public class PropertyParser {
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private Properties variables;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
}
public String handleToken(String content) {
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
}
}
四、总结
- 解析顺序和DTD约束是一致的;
- properties 配置的解析及其三种配置方式,resource和url不可以共存,但使用property子标签配置可以共存;
- XMLConfigBuilder 中的 XPathParser 类型对象 parser 属性,在解析过程中一直使用的是同一个,并且所解析的 variables 那个配置也一直在传递;
- variables 可以是properties 中所写的配置,也可以是调用SqlSessionBuilder对象中的build方法进行传入;
- ${content} 是通过字符串处理的方式和从 variables 查询 content 的方式进行处理的。