深入浅出Mybatis源码解析——全局配置文件解析(全局配置文件解析加载流程附篇)

前言

首先要说下停更这么久的原因,因为工作上最近有点忙,加上学习债的繁重以至于没有继续更新Mybatis源码解析的文章,所以趁着忙里偷闲,来简单的更新一篇。

在上一篇文章深入浅出Mybatis源码解析——全局配置文件解析加载流程中,我们讲到了全局配置文件的一个加载的过程,既然只是一个加载的流程,那就有解析的流程的啦!因此大家也猜到了,本篇正是讲全局配置文件中的一些解析,在parseConfiguration方法中涉及了很多的标签解析,由于数量比较多,所以我只会抽出几个比较典型的来给大家说一下。

废话不多说了,我们继续吧!

一、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);
      loadCustomLogImpl(settings);
      // 解析</typeAliases>标签
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析</plugins>标签
      pluginElement(root.evalNode("plugins"));
      // 解析</objectFactory>标签
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析</objectWrapperFactory>标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析</reflectorFactory>标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 把所获取到的setting属性放到Configuration对象中
      settingsElement(settings);
      
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析</environments>标签
      environmentsElement(root.evalNode("environments"));
      // 解析</databaseIdProvider>标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析</typeHandlers>标签
      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这个标签的属性来获取properties的节点,然后才加以解析。那我们来看下propertiesElement方法中的具体实现:

  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      // 获取子标签<property>标签的内容
      Properties defaults = context.getChildrenAsProperties();
      // 获取标签<properties>标签resource属性的值
      String resource = context.getStringAttribute("resource");
      // 获取标签<properties>标签url属性的值
      String url = context.getStringAttribute("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的值不为空,则加载对应的资源信息,然后合并到defaults中
      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);
    }
  }

上面的代码中,首先会判断XNode是否为null,如果不为null才会进行解析,从代码逻辑中得知,有如下几个流程:

  1. 获取子标签<property>标签的内容
  2. 获取标签<properties>标签resource属性的值
  3. 获取标签<properties>标签url属性的值
  4. 最后如果所获取到的resource、url属性的值不为null,则放到缓存中

看上去代码逻辑还是非常简单的,那就再看下这几个流程的代码具体实现,首先看第一步:

  public List<XNode> getChildren() {
    List<XNode> children = new ArrayList<>();
    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 Properties getChildrenAsProperties() {
	// 新建Properties对象
    Properties properties = new Properties();
    // 遍历字标签,并获取其name和value
    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;
  }

代码实现一如既往的比较简单,只是把所获取到的子标签进行遍历,并获取其name和value的值,如果不为空就放到Properties对象中,最后返回Properties对象。其实到这里properties标签的解析就已经基本完成了。

二、settings标签解析

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // 通过映射获取 本地映射工厂中的数据
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

  public Properties getChildrenAsProperties() {
    // 新建Properties对象
    Properties properties = new Properties();
    // 遍历字标签,并获取其name和value
    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;
  }

从代码逻辑上来讲,settings标签和properties标签并没有太多的差别。只是多了一步通过映射获取 本地映射工厂中的数据,然后来把这两个获取到的数据进行遍历比较,如果metaConfig来检查Properties对象中的key,则不符合则抛出异常。

 

总结:

这里只是选了两个,两个简单的标签解析进行说明,其实这篇文章只是为了要承接以下上篇文章中的一些不足,因此补上作为附篇文章,至于parseConfiguration方法中的mapperElement(root.evalNode("mappers"))方法,将会单独作为一篇文章来写。

发布了41 篇原创文章 · 获赞 8 · 访问量 4254

猜你喜欢

转载自blog.csdn.net/zfy163520/article/details/102519710