Mybatis原理---SqlSessionFactoryBuilder(获得配置文件)

in Mybatis原理 with 0 comment

在使用mybaits时,首先会创建一个SqlSessionFactory对象,该对象是由SqlSessionFactoryBuilder对象,调用该对象的build方法加载全局XML配置的流文件构建出一个SqlSessionFactory对象。

        //读取conf.xml
        Reader reader = Resources.getResourceAsReader("conf.xml");
        //创建会话工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder的源码

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

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.
      }
    }
  }

SqlSessionFactoryBuilder只有一堆重载的build方法,除了build(Configuration)方法,其他方法的参数都是输入流,最终由build(Configuration)方法生成SqlSessionFactory对象,在其中会生成一个XMLConfigBuilder对象,下面来看如何构建Configuration对象。

XMLConfigBuilder解析配置文件

XMLConfigBuilder类名就可以看出,这是用来解析XML配置文件的类,其父类为BaseBuilder。

public class XMLConfigBuilder extends BaseBuilder{
...
}

XMLConfigBuilder父类BaseBuilder

其中BaseBuilder还包含了许多子类,这些子类都是用来解析MyBatis各个配置文件,他们通过BaseBuilder父类共同维护一个全局的Configuration对象,
XMLConfigBuilder的作用就是解析全局配置文件,调用BaseBuilder其他子类解析其他配置文件,生成最终的Configuration对象。
值得注意的是,这里BaseBuilder构造方法参数是一个初始化的Configuration对象,Configuration对象初始化的时候,内置的别名注册器TypeAliasRegistry注册了默认的别名
image.png

public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

XPathParser类解析config.xml文件

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

上述代码可以发现在SqlSessionFactoryBuilder.build方法中,new XMLConfigBuilder类时 environment和props值为null,所以,我们主要看new XPathParser方法

字段

private Document document ; // Document 对象
private boolean validation; //是否开启验证
private EntityResolver entityResolver ; // 用于加载本地DTD 文件
pruvate Properties variables ; // mybatis -config.xml 中< propteries > 标签定义的键位对集合
private XPath xpath ; // XPath 对象

构造方法

XPathParser类提供了一系列的构造函数,所有的构造函数内部都通过通用的commonConstructor()方法实现对相关字段属性的初始化,部分构造函需要通过createDocument()方法把指定的数据源转换成Document对象。
大致可以分为四类

 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }

createDocument方法

该方法主要实现根据输入源创建Document对象。创建Document对象的过程如下

具体代码如下:

 private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
    	//创建DocumentBuilderFactory实例对象
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      //设置是否启用DTD验证
      factory.setValidating(validation);

      //设置是否支持XML名称空间
      factory.setNamespaceAware(false);

      //设置解析器是否忽略注释
      factory.setIgnoringComments(true);

      /**
       * 设置必须删除元素内容中的空格(有时也可以称作“可忽略空格”,请参阅 XML Rec 2.10)。
       * 注意,只有在空格直接包含在元素内容中,并且该元素内容是只有一个元素的内容模式时,
       * 才能删除空格(请参阅 XML Rec 3.2.1)。由于依赖于内容模式,因此此设置要求解析器处于验证模式。默认情况下,其值设置为 false。
       */
      factory.setIgnoringElementContentWhitespace(false);

      /**
       * 指定由此代码生成的解析器将把 CDATA 节点转换为 Text 节点,并将其附加到相邻(如果有)的 Text 节点。默认情况下,其值设置为 false。
       */
      factory.setCoalescing(false);

      /**
       * 指定由此代码生成的解析器将扩展实体引用节点。默认情况下,此值设置为 true。
       */
      factory.setExpandEntityReferences(true);

      //创建DocumentBuilder实例对象
      DocumentBuilder builder = factory.newDocumentBuilder();
      //指定使用 EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
      builder.setEntityResolver(entityResolver);

      //指定解析器要使用的 ErrorHandler。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

commonConstructor方法

构造器通用代码块,用于初始化validation、entityResolver、variables、xpath等属性字段。其中,validation、entityResolver、variables三个参数通过参数传递过来;xpath属性是通过XPathFactory创建。具体代码片段:

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

evalXXX方法

XPathParser类提供了一系列的evalXXX方法,见下图,这些方法主要用于解析boolean 、short、long 、int 、String 、Node等类型的信息。底层是通过evaluate()方法实现。其中,evalString()方法中,会通过调用PropertyParser.parse()处理占位符;evalNode()、evalNodes()方法中,根据解析结果会创建XNode对象。具体创建过程,在XNode类源码分析中学习。
image.png

XNode类

XNode类对应了配置文件中一个元素节点的信息。

  //org.w3c.dorn.Node对象
  private final Node node;

  //Node节点名称
  private final String name;

  //节点的内容
  private final String body;

  //节点属性集合
  private final Properties attributes;

  //配置文件中<properties>节点下定义的键位对
  private final Properties variables;

  //XPathParser对象,当前XNode对象由此XPathParser对象生成
  private final XPathParser xpathParser;

XMLConfigBuilder.parse()方法

此时我们已经得到了XMLConfigBuilder对象,再看SqlSessionFactoryBuilder的build方法,将XMLConfigBuilder实例对象parser调用parser()方法得到的Configuration实例对象config作为参数,调用SqlSessionFactory接口的实现类DefaultSqlSessionFactory构造出SqlSessionFactory对象。
XMLConfigBuilder对象在调用parser()方法时,会读出所有所有配置文件,将配置文件解析后保存在Configuration对象中。
image.png
再查看build方法,参数为Configuration,可以猜测处parse方法是返回一个Configuration对象
image.png
parse方法代码:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //参数是<configuraton>标签根节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

可以发现这里通过parseConfiguration方法使用之前创建的Xparser类,把XML全局配置文件中每一个节点的信息都读取出来,保存在一个Configuration对象中,Configuration分别对以下内容做出了初始化:

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

到了这里,已经可以开始加载配置文件了

总的来说,大概就是这个样子
image.png