• mybatis源码配置文件解析之一:解析properties标签


    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属性。

    原创不易,有不正之处欢迎指正。

  • 相关阅读:
    使用Boost::ptime构建高精度计时器
    static和extern
    通用js地址选择器
    js模拟抛出球运动
    前端用Webpact打包React后端Node+Express实现简单留言版
    webpack 打包一个简单react组件
    img及父元素(容器)实现类似css3中的background-size:contain / background-size:cover
    通用js函数集锦<来源于网络> 【二】
    通用js函数集锦<来源于网络/自己> 【一】
    向上滚动或者向下滚动分页异步加载数据(Ajax + lazyload)[上拉加载组件]
  • 原文地址:https://www.cnblogs.com/teach/p/12693588.html
Copyright © 2020-2023  润新知