`

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--公共的构造器方法。我们看图:

 

 

 

②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后,解析成文档树(文档工厂创建使用的设计模式就是典型的抽象工厂模式,大家可以学学)。我们看设置完属性的图:


 

 

 

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

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

其实,所有的配置文档解析都是一个套路,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。


 

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

 如图:


 

 

我们再看看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()));
    } 

如图:

 

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

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


 

三、【核心方法】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"))。

首先使用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));
}


 下面,从configuration对象中,获取到Variables内容,这个内容是啥?
Properties vars = this.configuration.getVariables();
if (vars != null) {
    defaults.putAll(vars);
 }
 就是我让大家注意的我们在XMLConfigBuilder构造函数中传递的props内容,如果我们传递了我们想定义的或者想覆盖的属性内容,那么这里就会起作用了---覆盖原有key或者新增key内容。
最后两行,将最终得到的属性hashTable设置给configuration对象和解析器XPathParse,供解析使用。
this.parser.setVariables(defaults);
 this.configuration.setVariables(defaults);
 2、elements标签的解析
elements标签的解析使用的方法是this.environmentsElement(root.evalNode("environments"));

 这里context的内容就是我们在db-core.xml中配置的数据源的环境信息---environments标签的所有信息。
<environments default="development">
        <environment id="development">
            <!-- 事务管理器的配置,这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域-->
            <transactionManager type="JDBC"/>
            <!-- 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”),POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
 因为我们没有传递environment的值,因此这里environment为null,所以在代码中执行了一步
if (this.environment == null) {
    this.environment = context.getStringAttribute("default");
}
这一步获取的environment为默认值,得到的environment=development。
我们接着分析,context.getChildren().iterator()获取到的是<environments>标签下子元素的遍历器,因为我们这里只配置了一个环境的<environment>,所以,得到的i$的大小为1。
Iterator i$ = context.getChildren().iterator();
while(i$.hasNext()) {
  XNode child = (XNode)i$.next();
  String id = child.getStringAttribute("id");
  if (this.isSpecifiedEnvironment(id)) {
      TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
      DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
      DataSource dataSource = dsFactory.getDataSource();
      Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
      this.configuration.setEnvironment(environmentBuilder.build());
  }
}
 通过遍历,得到child,这里的child就是<environments>标签下的一个个子<environment>标签。
使用child.getStringAttribute("id")获取到了该<environment>标签的id属性,这里我们定义的id为development(开发环境)。
通过方法this.isSpecifiedEnvironment(id),用来判断我们指定的environment和设置的id是否一致,如果一致,那么就使用该环境下的配置环境信息,如果不一致,进行下一次遍历。
private boolean isSpecifiedEnvironment(String id) {
        if (this.environment == null) {
            throw new BuilderException("No environment specified.");
        } else if (id == null) {
            throw new BuilderException("Environment requires an id attribute.");
        } else {
            return this.environment.equals(id);
        }
    }
 因为我们现在environment=development,而在子元素标签<environment>得到的id也是development,因此这里匹配成功。匹配成功后,解析该<environment>标签下的环境配置信息。
if (this.isSpecifiedEnvironment(id)) {
      TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
      DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
      DataSource dataSource = dsFactory.getDataSource();
      Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
      this.configuration.setEnvironment(environmentBuilder.build());
  }
 ①首先,得到TransactionFactory(事务工厂),这一步执行了方法this.transactionManagerElement(child.evalNode("transactionManager"));通过transactionManager标签获取它的type属性及该标签下的子元素。
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            TransactionFactory factory = (TransactionFactory)this.resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        } else {
            throw new BuilderException("Environment declaration requires a TransactionFactory.");
        }
    }
 光看代码没用,看个图就明白,各种属性值都有:

因为我们的transactionManager标签下没有设置<properties>属性,因此得到的props的大小为0。这里的事务工厂TransactionFactory的得到方式使用了resolveClass,根据type类型去找对应的实现类。底层是使用了BaseBuilder类中的typeAliasRegistry.resolveAlias(alias)方法,我们这里的type为JDBC,源码如下:
protected Class<?> resolveAlias(String alias) {
        return this.typeAliasRegistry.resolveAlias(alias);
    }
 得到的实现类为"jdbc" -> "class org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"。所以这里得到的事务工厂TransactionFactory的对应实现类为JdbcTransactionFactory。
②再次,得到数据源工厂,这里使用了方法this.dataSourceElement(child.evalNode("dataSource"));
方法源码如下:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        } else {
            throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }
    }
 看看,是不是跟事务工厂获取的过程都一模一样。
来个图看的更清晰一点,我们定义的标签:
<dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>

 最后根据类型POOLED得到的数据源工厂实现类为:
"pooled" -> "class org.apache.ibatis.datasource.pooled.PooledDataSourceFactory"。所以这里得到的数据源工厂的实现类就是PooledDataSourceFactory。然后使用事务工厂和数据源信息,得到环境builder,其中Builder为Environment的内部类:
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
根据环境的id,事务工厂,数据源,构造环境信息对象,
public Environment build() {
    return new Environment(this.id, this.transactionFactory, this.dataSource);
}
 并且设置到configuration对象中:this.configuration.setEnvironment(environmentBuilder.build());
 
五、综上所述,这就是SqlSessionFactory获取的整个过程,得到它以后,我们的长征才开始了,后续帖子逐一开始讲解。
 
  • 大小: 17.7 KB
  • 大小: 43.5 KB
  • 大小: 10.8 KB
  • 大小: 23.9 KB
  • 大小: 15.5 KB
  • 大小: 39.8 KB
  • 大小: 11.8 KB
  • 大小: 34.8 KB
  • 大小: 39.7 KB
  • 大小: 34.7 KB
  • 大小: 22.9 KB
  • 大小: 24.7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics