欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-15 10:54:13
...

看过此篇,你就赚到了。别人写的源码系列,虽然有源码,但是没有关键的图示和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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 

 

②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 MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 

 

 

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

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

其实,所有的配置文档解析都是一个套路,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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 

 其中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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 

 

我们再看看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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 

 

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

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

MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 

三、【核心方法】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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 

首先使用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诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 下面,从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"));
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 这里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.");
        }
    }
 光看代码没用,看个图就明白,各种属性值都有:
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
因为我们的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>
MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
 最后根据类型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获取的整个过程,得到它以后,我们的长征才开始了,后续帖子逐一开始讲解。
 
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 17.7 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 43.5 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 10.8 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 23.9 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 15.5 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 39.8 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 11.8 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 34.8 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 39.7 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 34.7 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 22.9 KB
  • MyBatis源码解析系列(二)--从SqlSessionFactory诞生说起
            
    
    博客分类: Mybatis MyBatis源码mybatisSqlSessionFactoryXMLConfigBuilder 
  • 大小: 24.7 KB