mybatis 作为日常开发的常用 ORM 框架, 在开发中起着很重要的作用, 了解其源码对日常的开发有很大的帮助. 源码版本为: 3-3.4.x, 可执行到 GitHub 进行下载.
从这篇文章开始逐一分析 mybatis 的核心配置文件(mybatis-config.xml), 今天先来看 properties 标签的解析过程.
一, 概述
在单独使用 mybatis 的时候, mybatis 的核心配置文件 (mybatis-config.xml) 就显的特别重要, 是整个 mybatis 运行的基础, 只有把配置文件中的各个标签正确解析后才可以正确使用 mybatis, 下面看 properties 标签的配置, properties 标签的作用就是加载 properties 文件或者 property 标签, 下面看其具体配置, 实例如下
- <properties resource="org/mybatis/example/config.properties">
- <property name="username" value="dev_user"/>
- <property name="password" value="F2Fa3!33TYyg"/>
- </properties>
上面是配置的 properties 标签的配置, 在标签中配置了 resource 属性和 property 子标签. 下面看具体的解析流程, 这里分析 properties 标签的解析过程, 启动流程暂不说, 直接看解析的代码.
二, 详述
上面, 看到了 properties 标签的配置, 下面看其解析方法, 这里只粘贴部分代码, 下面是 parseConfiguration 方法的代码,
- private void parseConfiguration(XNode root) {
- try {
- //issue #117 read properties first
- // 解析 properties 标签
- propertiesElement(root.evalNode("properties"));
- // 解析 settings 标签
- Properties settings = settingsAsProperties(root.evalNode("settings"));
- loadCustomVfs(settings);
- // 解析别名标签, 例 < typeAlias alias="user" type="cn.com.bean.User"/>
- typeAliasesElement(root.evalNode("typeAliases"));
- // 解析插件标签
- pluginElement(root.evalNode("plugins"));
- // 解析 objectFactory 标签, 此标签的作用是 mybatis 每次创建结果对象的新实例时都会使用 ObjectFactory, 如果不设置
- // 则默认使用 DefaultObjectFactory 来创建, 设置之后使用设置的
- objectFactoryElement(root.evalNode("objectFactory"));
- // 解析 objectWrapperFactory 标签
- objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
- // 解析 reflectorFactory 标签
- reflectorFactoryElement(root.evalNode("reflectorFactory"));
- settingsElement(settings);
- // read it after objectFactory and objectWrapperFactory issue #631
- // 解析 environments 标签
- environmentsElement(root.evalNode("environments"));
- databaseIdProviderElement(root.evalNode("databaseIdProvider"));
- typeHandlerElement(root.evalNode("typeHandlers"));
- // 解析 < mappers > 标签
- mapperElement(root.evalNode("mappers"));
- } catch (Exception e) {
- throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
- }
- }
从上面的代码中可以找到下面的代码, 即为解析的代码,
propertiesElement(root.evalNode("properties"));
这个方法就是解析 properties 标签, 下面看具体的解析过程.
1, 解析子标签和属性
- /**
- * 解析 mybatis-config.xml 文件中的 properties 标签
- *<properties resource="org/mybatis/example/config.properties">
- *<property name="username" value="dev_user"/>
- *<property name="password" value="F2Fa3!33TYyg"/>
- *</properties>
- * 解析步骤:
- *1, 解析配置的 property 标签, 放到 defaults 中;
- *2, 解析 resource 或 url 属性, 放到 defaults 中;
- *3, 获取 configuration 中的 variables 变量值, 放到 defaults 中
- * @param context
- * @throws Exception
- */
- private void propertiesElement(XNode context) throws Exception {
- if (context != null) {
- //1, 读取 properties 标签中的 property 标签 < property name=""value=""/>
- Properties defaults = context.getChildrenAsProperties();
- //2, 读取 properties 标签中的 resource,url 属性
- String resource = context.getStringAttribute("resource");
- String url = context.getStringAttribute("url");
- //resource 和 url 属性不能同时出现在 properties 标签中
- 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 不为空, 则解析为 properties, 放到 defaults 中, 由于 defaults 是 key-value 结构, 所以会覆盖相同 key 的值
- if (resource != null) {
- defaults.putAll(Resources.getResourceAsProperties(resource));
- } else if (url != null) {// 如果 url 不为空, 则解析为 properties, 放到 defaults 中, 由于 defaults 是 key-value 结构, 所以会覆盖相同 key 的值
- defaults.putAll(Resources.getUrlAsProperties(url));
- }
- //3, 获得 configuration 中的 variables 变量的值, 此变量可以通过 SqlSessionFactoryBuilder.build()传入 properties 属性值
- Properties vars = configuration.getVariables();
- // 如果调用 build 的时候传入了 properties 属性, 放到 defaults 中
- if (vars != null) {
- defaults.putAll(vars);
- }
- // 放到 parser 和 configuration 对象中
- parser.setVariables(defaults);
- configuration.setVariables(defaults);
- }
- }
从上面的解析过程可以看到, 首先解析 properties 标签的子标签, 也就是 property 标签, 通过下面的方法获得,
- //1, 读取 properties 标签中的 property 标签 < property name=""value=""/>
- Properties defaults = context.getChildrenAsProperties();
解析 property 标签, 并放到 Properties 对象中. 那么是如何防盗 Properties 对象中的那, 在 getChildrenAsProperties 方法中,
- 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;
- }
可以看出是循环 property 标签, 获得其 name 和 value 属性, 并放入 properties 对象中.
接着解析 properties 的 resource 和 url 属性, 如下
- //2, 读取 properties 标签中的 resource,url 属性
- String resource = context.getStringAttribute("resource");
- String url = context.getStringAttribute("url");
分别获得 resource 和 url 属性, 这里这两个属性都是一个路径.
2, 处理属性
下面看这个判断,
- //resource 和 url 属性不能同时出现在 properties 标签中
- 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.");
- }
这个判断表明在 properties 标签中, resource 和 url 属性不能同时出现.
2.1, 处理 resource 和 url 属性
下面看 resource 和 url 属性的处理, 这里 resource 和 url 两个属性都是代表的一个路径, 所以这里肯定是需要读取相应路径下的文件.
- // 如果 resource 不为空, 则解析为 properties, 放到 defaults 中, 由于 defaults 是 key-value 结构, 所以会覆盖相同 key 的值
- if (resource != null) {
- defaults.putAll(Resources.getResourceAsProperties(resource));
- } else if (url != null) {// 如果 url 不为空, 则解析为 properties, 放到 defaults 中, 由于 defaults 是 key-value 结构, 所以会覆盖相同 key 的值
- defaults.putAll(Resources.getUrlAsProperties(url));
- }
下面看对 resource 的处理, 调用的 Resources.getResourceAsProperties(resource))方法, 对 resource 进行处理,
- public static Properties getResourceAsProperties(String resource) throws IOException {
- Properties props = new Properties();
- InputStream in = getResourceAsStream(resource);
- props.load(in);
- in.close();
- return props;
- }
从上面的代码可以看出是要转化为 InputStream, 最后放到 Properties 对象中, 这里加载文件的详细过程, 后面再详细分析.
下面看对 url 的处理, 调用 Resources.getUrlAsProperties(url)方法, 对 url 进行处理,
- public static Properties getUrlAsProperties(String urlString) throws IOException {
- Properties props = new Properties();
- InputStream in = getUrlAsStream(urlString);
- props.load(in);
- in.close();
- return props;
- }
上面的代码依然是把 url 代表的文件处理成 Properties 对象.
2.3, 处理已添加的 Properties
在上面处理完 property 子标签, resource 和 url 属性后, 还进行了下面的处理, 即从 configuration 中获得 properties,
- //3, 获得 configuration 中的 variables 变量的值, 此变量可以通过 SqlSessionFactoryBuilder.build()传入 properties 属性值
- Properties vars = configuration.getVariables();
- // 如果调用 build 的时候传入了 properties 属性, 放到 defaults 中
- if (vars != null) {
- defaults.putAll(vars);
- }
如果 configuration 中已经存在 properties 信息, 则取出来, 放到 defaults 中.
2.4, 放入 configuration 对象中
经过上面的处理, 最后把所有的 properties 信息放到 configuration 中,
- // 放到 parser 和 configuration 对象中
- parser.setVariables(defaults);
- configuration.setVariables(defaults);
把 defaults 放到了 configuration 的 variables 属性中, 代表的是整个 mybatis 环境中所有的 properties 信息. 这个信息可以在 mybatis 的配置文件中使用 ${key}使用, 比如,${username}, 则会从 configuration 的 variables 中寻找 key 为 username 的属性值, 并完成自动属性值替换.
三, 总结
上面分析了 properties 标签的解析过程, 先解析 property 标签, 然后是 resource,url 属性, 最后是生成 SqlSessionFactory 的使用调用 SqlSessionFactoryBuilder 的 build 方法, 传入的 properties, 从上面的解析过程, 可以知道如果存在重复的键, 那么最先解析的会被后面解析的覆盖掉, 也就是解析过程是: property 子标签 -->resource-->url-->开发者设置的, 那么覆盖过程为: 开发者设置的 -->url-->resource-->property 子标签, 优先级最高的为开发者自己设置的 properties 属性.
原创不易, 有不正之处欢迎指正.
来源: https://www.cnblogs.com/teach/p/12693588.html