Mybatis源码学习(4)-解析器模块之<properties>标签应用

<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 &quot; 格 式 的 , 且 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 ( ) 方 法 处 理 &quot; {xxx}&quot;格式的,且variables的值即为configuration实例对象的variables字段值。在PropertyParser.parse()中,首先定义了VariableTokenHandler实例对象,然后又定义了GenericTokenParser实例对象,最后执行GenericTokenParser实例对象的parse()方法。具体过程如下:在GenericTokenParser实例对象的parse()方法处理&quot; xxx"variablesconfigurationvariablesPropertyParser.parse()VariableTokenHandlerGenericTokenParserGenericTokenParserparse()GenericTokenParserparse()"{xxx}“格式的参数。根据前面分析该方法的作用,最终是把”${xxx}“格式的参数解析成了"xxx”,并赋值给了expression变量。然后在其中调用了VariableTokenHandler实例对象的handleToken()方法,并把expression变量的值作为参数进行传递。最后,在handleToken()方法中,根据expression变量的值,即变量key,再从variables变量中取出key对应的参数值。然后进行返回使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值