MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起

看过此篇,你就赚到了。别人写的源码系列,虽然有源码,但是没有关键的图示和debug过程,只是讲了有啥用,不适用。在我写的MyBatis源码系列中,都会结合debug过程+图示来阐述,我们从SqlSessionFactoryBuilder说起。SqlSessionFactoryBuilder,见名知意,是SqlSessionFactory的建造者(Builder)。那么我们猜想,既然是建造SqlSessionFactory,如果让我去写,那么总需要提供一个全参数的建造方法和一些特定参数的建造方法。我们的猜想对与否?验证一下。

一、SqlSessionFactoryBuilder源码解析

在SqlSessionFactoryBuilder类中,我们可以看到如下的构造SqlSessionFactory的方法build(我们这里只说包含Reader的方法,InputStream分析方法类似):

public SqlSessionFactory build(Reader reader) {
        return this.build((Reader)reader, (String)null, (Properties)null);
    }

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

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

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

 果不其然,跟我们的猜想一样,SqlSessionFactoryBuilder提供了构造SqlSessionFactory全量参数方法public SqlSessionFactory build(Reader reader, String environment, Properties properties),也提供了根据不同特殊要求提供的构造SqlSessionFactory的方法。其实,这一点都不需要惊讶。如果你们研究过其他框架的源码,就知道,这是一种很有效的方式,在jdk的源码中,很多的设计思路跟它一模一样,此处大家可以借鉴。

那我们按照总分的路线去深入研究build(建造)SqlSessionFactory 的方法,那就从全量参数方法开始整。

在全量参数方法build中,我们需要关注的只有两行:

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());

①XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

reader是一个文件流读取器,这是将文件转换为流后进行数据配置节点读取的。我们在解析xml中常用到。

environment是一个环境参数,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,这是用来实现多数据源配置使用的,默认情况下,使用默认default。在解析<environments>元素时就会看到。

properties作为可选参数,是用来进行属性覆盖的。如果我们想覆盖xml中<properties>中配置的某些属性配置,这个参数会用到。口说无凭,在解析<properties>标签内容时候,我们会看到。

②var5 = this.build(parser.parse());

这行是根据xml配置文件的流读取器、使用的环境、设置的属性来得到XMLConfigBuilder,它的作用是什么?就是用来执行解析任务--解析我们篇一中db-core.xml中的内容。

二、XMLConfigBuilder(reader, environment, properties)解密

XMLConfigBuilder的构造函数如下(我们这里只分析使用Reader的构造函数,使用InputStream的同理):

public XMLConfigBuilder(Reader reader) {
        this((Reader)reader, (String)null, (Properties)null);
    }

    public XMLConfigBuilder(Reader reader, String environment) {
        this((Reader)reader, environment, (Properties)null);
    }

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

在XMLConfigBuilder中,我们也直接看最长的全参数的构造函数:

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

 在这里,又构造了XPathParser,我们看下它的构造函数:

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

这里四个参数分别用在了两个方法中(这两方法都在XPathParser类中),我们分别看一下:

①commonConstructor方法:

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

在commonConstructor方法中,将构造出的实体查找器entityResolver(XMLMapperEntityResolver)设置给了XPathParser的entityResolver属性,构造了XPathFactory,以及使用XPathFactory构造了节点解析的xpath属性,所以它起了一个名字叫commonConstructor--公共的构造器方法。我们看图:

 
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 

②createDocument方法:

private Document createDocument(InputSource inputSource) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(this.validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(this.entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

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

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

 一看设置的这一堆估计一圈人都懵了,其实它就是指定工厂类按照何种规则去解析xml文档,没啥特殊的,大家可以自己写个示例用它去解析跟踪文档格式,这里就不细说。方法最后一句,builder.parse(inputSource),将我们的Reader包装成inputSource后,解析成文档树(文档工厂创建使用的设计模式就是典型的抽象工厂模式,大家可以学学)。我们看设置完属性的图:

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起

我们可以看到,一行代码涉及到的后续过程挺多,但是,没什么可怕的。你深入研究后还觉得难吗?

这一行的作用就是完成了两个关键步骤:准备解析器,准备好解析的文档树。

其实,所有的配置文档解析都是一个套路,Spring中的源码解析也一样。

得到XPathParser后,我们XMLConfigBuilder构造函数的内容就全了,看代码:

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

 这此构造函数中, 首先调用了XMLConfigBuilder父类的构造函数,传入了Configuration对象,我们看看这个对象的构造函数:

public Configuration() {
        this.safeRowBoundsEnabled = false;
        this.safeResultHandlerEnabled = true;
        this.mapUnderscoreToCamelCase = false;
        this.aggressiveLazyLoading = true;
        this.multipleResultSetsEnabled = true;
        this.useGeneratedKeys = false;
        this.useColumnLabel = true;
        this.cacheEnabled = true;
        this.callSettersOnNulls = false;
        this.localCacheScope = LocalCacheScope.SESSION;
        this.jdbcTypeForNull = JdbcType.OTHER;
        this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));
        this.defaultExecutorType = ExecutorType.SIMPLE;
        this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        this.variables = new Properties();
        this.objectFactory = new DefaultObjectFactory();
        this.objectWrapperFactory = new DefaultObjectWrapperFactory();
        this.mapperRegistry = new MapperRegistry(this);
        this.lazyLoadingEnabled = false;
        this.interceptorChain = new InterceptorChain();
        this.typeHandlerRegistry = new TypeHandlerRegistry();
        this.typeAliasRegistry = new TypeAliasRegistry();
        this.languageRegistry = new LanguageDriverRegistry();
        this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
        this.caches = new Configuration.StrictMap("Caches collection");
        this.resultMaps = new Configuration.StrictMap("Result Maps collection");
        this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");
        this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
        this.loadedResources = new HashSet();
        this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");
        this.incompleteStatements = new LinkedList();
        this.incompleteCacheRefs = new LinkedList();
        this.incompleteResultMaps = new LinkedList();
        this.incompleteMethods = new LinkedList();
        this.cacheRefMap = new HashMap();
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
        this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
        this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
        this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        this.languageRegistry.register(RawLanguageDriver.class);
    }

不要被这么长一堆吓住,其实很简单,就是对Configuration类对象属性进行初始化, this.typeAliasRegistry.registerAlias("xxx", xxx.class);只是给对应的注册类起了个别名。上面的内容我们用到再说。

我们回到XMLConfigBuilder构造函数:

构造函数第一句,super(new Configuration());我们看下XMLConfigBuilder父类BaseBuilder的构造函数:

public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

 这里初始化了三个参数:configuration,typeAliasRegistry, typeHandlerRegistry。

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 

 其中typeAliasRegistry, typeHandlerRegistry就是使用了Configuration类构造函数初始化的对象:

this.typeHandlerRegistry = new TypeHandlerRegistry();
 this.typeAliasRegistry = new TypeAliasRegistry();

一个是类型处理器注册类,一个是类型别名注册类,我们可以看下他们各自的构造函数。

TypeAliasRegistry别名注册类:它只是对我们常见的基本类型,数组等做了一个,保存在一个Map中(Map<String, Class<?>> TYPE_ALIASES)。(注册的含义不要想得太深奥,跟我们常见的网站注册一样,只是将你的基本信息录入,作为后续鉴别使用)

public TypeAliasRegistry() {
        this.registerAlias("string", String.class);
        this.registerAlias("byte", Byte.class);
        this.registerAlias("long", Long.class);
        this.registerAlias("short", Short.class);
        this.registerAlias("int", Integer.class);
        this.registerAlias("integer", Integer.class);
        this.registerAlias("double", Double.class);
        this.registerAlias("float", Float.class);
        this.registerAlias("boolean", Boolean.class);
        this.registerAlias("byte[]", Byte[].class);
        this.registerAlias("long[]", Long[].class);
        this.registerAlias("short[]", Short[].class);
        this.registerAlias("int[]", Integer[].class);
        this.registerAlias("integer[]", Integer[].class);
        this.registerAlias("double[]", Double[].class);
        this.registerAlias("float[]", Float[].class);
        this.registerAlias("boolean[]", Boolean[].class);
        this.registerAlias("_byte", Byte.TYPE);
        this.registerAlias("_long", Long.TYPE);
        this.registerAlias("_short", Short.TYPE);
        this.registerAlias("_int", Integer.TYPE);
        this.registerAlias("_integer", Integer.TYPE);
        this.registerAlias("_double", Double.TYPE);
        this.registerAlias("_float", Float.TYPE);
        this.registerAlias("_boolean", Boolean.TYPE);
        this.registerAlias("_byte[]", byte[].class);
        this.registerAlias("_long[]", long[].class);
        this.registerAlias("_short[]", short[].class);
        this.registerAlias("_int[]", int[].class);
        this.registerAlias("_integer[]", int[].class);
        this.registerAlias("_double[]", double[].class);
        this.registerAlias("_float[]", float[].class);
        this.registerAlias("_boolean[]", boolean[].class);
        this.registerAlias("date", Date.class);
        this.registerAlias("decimal", BigDecimal.class);
        this.registerAlias("bigdecimal", BigDecimal.class);
        this.registerAlias("biginteger", BigInteger.class);
        this.registerAlias("object", Object.class);
        this.registerAlias("date[]", Date[].class);
        this.registerAlias("decimal[]", BigDecimal[].class);
        this.registerAlias("bigdecimal[]", BigDecimal[].class);
        this.registerAlias("biginteger[]", BigInteger[].class);
        this.registerAlias("object[]", Object[].class);
        this.registerAlias("map", Map.class);
        this.registerAlias("hashmap", HashMap.class);
        this.registerAlias("list", List.class);
        this.registerAlias("arraylist", ArrayList.class);
        this.registerAlias("collection", Collection.class);
        this.registerAlias("iterator", Iterator.class);
        this.registerAlias("ResultSet", ResultSet.class);
    }

 如图:

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 

我们再看看TypeHandlerRegistry类型处理器注册类,它是将java类型和JdbcType都标记出对应的类型处理器,并且把对应的类型处理器都注册在各自的Map中:

public TypeHandlerRegistry() {
        this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));
        this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));
        this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler()));
        this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler()));
        this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler()));
        this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler()));
        this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler()));
        this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler()));
        this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler()));
        this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler()));
        this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler()));
        this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
        this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
        this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
        this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
        this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
        this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler()));
        this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler()));
        this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler()));
        this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
        this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler()));
        this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
        this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
        this.register(Object.class, this.UNKNOWN_TYPE_HANDLER);
        this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
        this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler()));
        this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));
        this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
        this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
        this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler()));
        this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler()));
        this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler()));
        this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler()));
        this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler()));
    } 

如图:

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起

最终设置完的XMLConfigBuilder属性如图,其中最关键的就是configuration,这是所有解析配置的核心的核心。

这里props我们没有传值为null,到了解析properties标签属性时,大家就可以看到这个值做什么用了。

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 

三、【核心方法】this.build(parser.parse())解密

我们先看parser.parse()。parser指的是我们第一步得到的XMLConfigBuilder,在它的parse方法中:

public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

 里面的关键方法this.parseConfiguration(this.parser.evalNode("/configuration")):

this.parser.evalNode("/configuration")标记出根节点,然后从根节点开始解析各属性。

这里就是MyBatis的核心配置文件中可以配置的所有节点啦!!

比如,我们配置的properties节点,就是使用 this.propertiesElement(root.evalNode("properties"));来解析,这里面就要用到我们核心的核心---configuration对象,一个对象包含了很多信息。这一个解析方法包含了我们db-core.xml文件的所有解析内容。

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.settingsElement(root.evalNode("settings"));
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

 这里的每一个解析方法都可以作为作为一节来讲,我们后续帖逐渐来阐述。

解析完核心配置文件的所有标签后,我们的configuration对象信息就都完善了,此时SqlSessionFactoryBuilder的build开始执行,去得到SqlSessionFactory,这里使用了DefaultSqlSessionFactory。

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

 在这里,只是把Configuration对象传到了DefaultSqlSessionFactory的构造方法中,这样SqlSessionFactory接口终于有了自己的实现类DefaultSqlSessionFactory。

以上就是SqlSessionFactory获取的所有步骤,虽然过程繁杂,其实很简单。

四、properties标签解析和elements标签解析

先来说两个我们用到的,properties标签解析和elements标签解析,解答我们开头说的问题。

1、properties标签的解析

properties标签的解析使用的方法是this.propertiesElement(root.evalNode("properties"))。

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起

首先使用root.evalNode("properties")得到我们properties标签的内容,因此context值为:

<properties resource="db-info.properties"/>

其中,Properties defaults = context.getChildrenAsProperties()是用来获取properties标签下的子元素内容的,比如:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

通过这一行代码,就可以获取到property元素内容,存放在Properties(它是一个hashTable)中。

这里我们没有子元素,因此,defaults中没有内容。

我们继续往下,在properties标签中,我们使用了resource属性,未使用url属性。

String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");

 因此,上面得到的resource值为db-info.properties,url为null。下面的判断也告诉我们,在properties标签中,resource和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不为空,使用Resources.getResourceAsProperties(resource)获取到属性的key-value值,全部放在defaults中。在db-info.properties中,我们放了数据源的四个属性,因此,这里最后的大小为4。

if (resource != null) {
    defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
    defaults.putAll(Resources.getUrlAsProperties(url));
}

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
 下面,从configuration对象中,获取到Variables内容,这个内容是啥?