Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)

SqlSessionFactoryBuilder.build创建SqlSessionFactory(粗略走一步流程)

看完上篇文章后,你对mybatis应该有个大概的了解了,那么我们知道new SqlSessionFactoryBuilder().build是框架的入口,我们到SqlSessionFactoryBuilder类里看到里面其实都是build函数的。

全都是build重载函数。上面几个重载其实最终都是调用了build(Reader reader, String environment, Properties properties)这个方法。下面的几个则是调用了build(InputStream inputStream, String environment, Properties properties) 。

最终都会调用最后一个build(Configuration config)方法,把创建好的Configuration赋给DefaultSqlSessionFactory里面的Configuration字段,所以这里的逻辑其实非常简单,就是解析传入配置文件的Reader或者inputStream,然后生成Configuration,赋值给新建出来的DefaultSqlSessionFactor中的configuration字段,然后返回DefaultSqlSessionFactor。

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

下面,我们来完整的走一个流程,就以上一篇的demo为例

//第一步
    public static SqlSession getSqlsession(){
        //获取mybatis,config的xml文件的输入流
        InputStream config = MybatisUtil.class.getClassLoader().getResourceAsStream("mybatis.cfg.xml");
        //使用SqlSessionFactory build(InputStream inputStream);来获取SqlSessionFactory
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(config);
        return build.openSession();
    }

//第二步调用SqlSessionFactoryBuilder中的build(InputStream inputStream)方法
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  //第三部调用SqlSessionFactoryBuilder中的build(InputStream inputStream, String environment, Properties properties)方法
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    //把输入流解析成可以用做分析的document,以及准备其他解析需要的东西
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parser.parse()生成出Configuration类,最后调用build(Configuration config)方法来生成DefaultSqlSessionFactory,并返回
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  //第四步调用SqlSessionFactoryBuilder中的 build(Configuration config) 方法
    public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

这是最上面的一层,逻辑还是非常简单的,一句话就能概括,就是从给定的配置文件中解析出Configuration,然后生成DefaultSqlSessionFactory。(如果要指定环境,和特定的属性的话用另外2个build 的重载方法)

我们先进入最后一个build看一下,new DefaultSqlSessionFactory(config);究竟做了什么。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
  //非常简单,就是生成了一个DefaultSqlSessionFactory 类,然后把里面的configuration字段,赋值为传入的configuration。
    this.configuration = configuration;
  }
  //后面的函数就不列出来了
  }

build是如何通过xml文件来生成Configuration的(比较详细的分析流程)

所以build的重点在解析xml成Configuration 类这个地方。
也就是下面这两句话

 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 parser.parse();

ok,第一句话是生成了一个XMLConfigBuilder类,我们来看看XMLConfigBuilder到底是什么样的。只列出字段和关键方法

public class XMLConfigBuilder extends BaseBuilder {

      private boolean parsed;
      private XPathParser parser;
      private String environment;
      private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
      //上层调用到这个方法,然后他调用到了下面的重载方法
      public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
          //这里只有new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())需要看一下
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
      //其实就是给关键的几个字段赋值,这里的第一句调用了父类的构造函数,传入了一个新的Configuration,也就是说到这步的时候,我们用来返回的Configuration对象已经有了,但是Configuration的值还没有构造好。
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
  }
  public abstract class BaseBuilder {
      protected final Configuration configuration;
      protected final TypeAliasRegistry typeAliasRegistry;
      protected final TypeHandlerRegistry typeHandlerRegistry;

}

接下来我们来看看XPathParser类

public class XPathParser {

  private Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
      //为类的其他字段赋值
    commonConstructor(validation, variables, entityResolver);
    //根据输入流创建一个Document ,并赋值给 document字段(之后的解析就靠这里面的信息了)
    this.document = createDocument(new InputSource(inputStream));
  }

}

xml解析成Document的具体过程LZ就不带大家深入研究了,有兴趣的朋友可以自己跟下去看看。

接下来我们回到SqlSessionFactoryBuilder类。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    //现在我们已经拿到parser了
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //接下来要去看parse()是怎么创建出Configuration的
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析靠这步,首先拿到configuration节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {
  //拿到configuration节点
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    //返回封装成XNode类
    return new XNode(this, node, variables);
  }
public class XNode {

  private Node node;
  private String name;
  private String body;
  private Properties attributes;
  private Properties variables;
  private XPathParser xpathParser;

  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);
  }
}

XPathParser类的实际值
Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)

//接下来就要解析并给configuration赋值拉。因为configuration在新建XMLConfigBuilder的时候就已经创建好了,解析的过程其实就是赋值的过程(我们在xml文件里配置的各个节点都在下面这个函数里解析)
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这是xml文件中要的标签,和上面代码一对比,你就会清楚的明白,上面每一个方法其实就是把每一个标签转换成configuration。
Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)

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");


      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,键相同则替换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的Variables
      parser.setVariables(defaults);
      //设置configuration的Variables
      configuration.setVariables(defaults);
    }
  }

其他的解析其实和properties的解析差不多。根据配置文件节点的信息设置configuration。

因为mybatis非常重要的一个点就是在mapper上,所以楼主之后会有一篇专门写mapper是什么解析和使用的。所以这里大家可以简单过一下mapper的解析过程。

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

          if (resource != null && url == null && mapperClass == null) { //根据resource解析
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {//根据url 解析
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {//根据mapperClass解析
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

套路其实差不多,就不细细的深入看了。但是mybatis的mapper的实现方法还是要细细讨论的,让我们在之后的文章中在细致的学习以下。

小结

看了加载过程的源码,我们在这至少了解到了mybatis是如何解析xml文件的,还有properties,假如我们即在xml文件中直接配置的属性和又设置了resource(或者url)的话,resource(或者url)中的属性会覆盖xml文件中直接配的(key相同的情况下才会覆盖,否则是累加的情况),若详细看过mapper的创建过程的话,还会知道在xml中配的mapper的语句会被注解中配置的语句覆盖。为什么会有这些特性?因为源码的读取步骤以及策略如此,所以要善于利用

上一篇:js获取当前月第一天和下个月第一天


下一篇:第一次代码行数破百——打印1966年以后的月份的程序