前面说过, XmlConfigBuilder
对象主要用于解析mybatis的全局配置文件,并以此来获取 Configuration
对象的实例。
XmlConfigBuilder
对外暴露了六个构造方法,这六个方法根据mybatis配置文件的输入流类型可以分为两大类:
分别负责处理字节流形式的配置文件和处理字符流形式的配置文件。
// 处理字节流类型的mybatis配置 XMLConfigBuilder(InputStream inputStream); XMLConfigBuilder(InputStream inputStream, String environment); XMLConfigBuilder(InputStream inputStream, String environment, Properties props); // 处理字符流类型的mybatis配置 XMLConfigBuilder(Reader reader); XMLConfigBuilder(Reader reader, String environment); XMLConfigBuilder(Reader reader, String environment, Properties props); 复制代码
在具体的代码实现上,这两类构造方法的调用最终会根据文件流的不同落在下面这两个方法上(因为前面我们传入的是字节流,所以此处触发的是第一个方法):
/** * @param inputStream 配置文件输入流 * @param environment 当前环境ID * @param props 用户自定义属性 */ public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser( inputStream,/*XML文件输入流*/ true, /*开启验证*/ props, /*Property参*/ new XMLMapperEntityResolver() /*XML实体解析器*/ ) /*新建一个XML解析器的实例*/ , environment/*环境对象*/ , props /*Property参数*/ ); } 复制代码
/** * @param reader 配置文件输入流 * @param environment 当前环境ID * @param props 用户自定义属性 */ public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, true/*是否进行DTD校验*/ , props /*参数*/ , new XMLMapperEntityResolver() /*XML对应的DTD文件的寻找器*/ )/*构建一个XPath解析器*/ , environment /*当前环境*/ , props /*参数*/ ); } 复制代码
在上面这两个方法内,不管传入的是字节流还是字符流,mybati都会使用传入的配置文件输入流创建一个 XPathParser
对象,然后利用 XPathParser
对象调用 XMLConfigBuilder
的私有构造方法 XMLConfigBuilder(XPathParser parser, String environment, Properties props)
来完成 XmlConfigBuilder
对象的构造过程。
/** * @param parser XPath解析器 * @param environment 环境ID * @param props 用户自定义属性 */ private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 初始化Configuration对象的同时注册部分别名以及语言驱动 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); // 设置变量 this.configuration.setVariables(props); // 初始化解析标签 this.parsed = false; // 初始化环境容器 this.environment = environment; // 初始化Xml地址解析器 this.parser = parser; } 复制代码
XPathParser
是mybatis内部定义的一个基于 XPath
语言来读取 XML
文档中的数据的解析工具类。
XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。
mybatis在创建 XPathParser
对象时,传入了一个 new XMLMapperEntityResolver()
对象,该对象的作用我们后面再说,现在我们先忽略掉他以及 XPathParser
对象的具体实现,继续看 XmlConfigBuilder
的构造过程。
new XPathParser(reader, true/*是否进行DTD校验*/ , props /*参数*/ , new XMLMapperEntityResolver() /*XML对应的DTD文件的寻找器*/ ) 复制代码
在 XmlConfigBuilder
的私有构造方法中,mybatis会调用 Configuration
的无参构造方法生成一个 Configuration
对象,并将 Configuration
对象传入 XmlConfigBuilder
的父类 BaseBuilder
的构造方法中完成 BaseBuilder
的初始化工作。
// 初始化Configuration对象的同时注册部分别名以及语言驱动 super(new Configuration()); 复制代码
Configuration
的无参构造方法内包含了一些基础数据的准备工作,其中主要包括注册常用的类型别名,注册脚本语言驱动器和配置默认的脚本语言驱动器。
类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
可以查看简单的实例测试: org.apache.ibatis.submitted.language.LanguageTest#testLangVelocityWithMapper
public Configuration() { // 注册别名 // 注册JDBC别名 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); // 注册事务管理别名 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); // 注册JNDI别名 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); // 注册处理数据库ID的提供者 typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); // 注册基于XML的语言驱动 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); //注册静态语言驱动(通常无需使用) typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); // 注册Sl4j日志 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); //注册Commons日志 typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); //注册log4j日志 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); //注册log4j2日志 typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); //注册jdk log日志 typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); //注册标准输出日志 typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); //注册无日志 typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); // 注册CGLIB typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); // 注册JAVASSIST typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); // 默认使用XML语言驱动 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); // 支持原始语言驱动 languageRegistry.register(RawLanguageDriver.class); } 复制代码
!! 需要留意的是,在这里mybatis定义了 默认的脚本语言驱动器 是别名为 XML
的 XMLLanguageDriver 。
我们之前说过 XMLConfigBuilder
对象是 BaseBuilder
的子类,在 BaseBuilder
里有三个 final
修饰的属性:
/** * Mybatis配置信息 */ protected final Configuration configuration; /** * 类型别名注册表 */ protected final TypeAliasRegistry typeAliasRegistry; /** * 类型转换处理器注册表 */ protected final TypeHandlerRegistry typeHandlerRegistry; 复制代码
他们的初始化赋值操作发生在 BaseBuilder
的构造方法中:
public BaseBuilder(Configuration configuration) { // 保持引用 this.configuration = configuration; // 从Mybatis配置中同步过来类型别名注册表 this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); // 从Mybatis配置中同步过来类型处理器注册表 this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } 复制代码
在上面的这个构造方法中, BaseBuilder
缓存了对 Configuration
对象的引用,同时把自身的 typeHandlerRegistry
和 TypeAliasRegistry
属性指向了 Configuration
对象的 typeHandlerRegistry
和 TypeAliasRegistry
属性。
public class Configuration { ... /** * 类型处理器注册表 */ protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); /** * 类型别名注册表,主要用在执行SQL语句的出入参以及一些类的简写 */ protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); ... } 复制代码
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
在调用 BaseBuilder
的构造方法之后, XMLConfigBuilder
会将用户自定义的变量集合传递给 Configuration
对象,让其保存起来,供后面解析配置时使用。
// 缓存用户定义的变量 this.configuration.setVariables(props); 复制代码
之后重置解析标记,将其状态修改为 未进入解析操作
,表示允许调用 parse()
方法:
// 初始化解析标签 this.parsed = false; 复制代码
之后记录当前使用的环境信息,便于后面解析配置时使用:
// 初始化环境容器 this.environment = environment; 复制代码
然后,保存当前使用的Xml地址解析器:
// 初始化Xml地址解析器 this.parser = parser; 复制代码
到这,就完成了 XMLConfigBuilder
对象的构造过程,除了在构造方法中涉及到的几个属性外, XMLConfigBuilder
还有一个常量属性 localReflectorFactory
,该属性在代码中硬编码为 DefaultReflectorFactory
,他的作用后面我们会学到,先不管他:
/** * 用于创建{@link org.apache.ibatis.reflection.Reflector}对象的工厂 */ private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory(); 复制代码
现在我们先回过头来继续看 XMLConfigBuilder
依赖的 XPathParser
对象。
XPathParser
的构造方法有很多,除了需要用户传入对应的 XML
文档之外,还可以接收一些其他可选传的参数。
在这里,传入的XML文档可以有多种形式,可以是XML的地址链接,输入流,甚至是Document对象都可以。
可选传的参数有三个:
第一个是 boolean
类型的 validation
,该参数表示是否对 XML
文档进行 DTD
校验,默认值为 false
。
第二个参数是 Properties
类型的 variables
,该参数表示用户自定义的属性对象,这些属性对象可以在整个配置文件中以占位符的形式被读取和使用。
第三个参数是 EntityResolver
类型的 entityResolver
对象,该参数的作用是为调用方寻找DTD验证文件。
EntityResolver
这个参数我们前面有提到过,他在 XmlConfigBuilder
构造方法中被硬编码为 XMLMapperEntityResolver
类型的对象。
new XPathParser(reader, true/*是否进行DTD校验*/ , props /*参数*/ , new XMLMapperEntityResolver() /*XML对应的DTD文件的寻找器*/ )/*构建一个XPath解析器*/ 复制代码
他的代码比较简单,定义了六个常量:
/** * IBATIS 全局配置文件的systemId(用于兼容IBATIS) */ private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd"; /** * IBATIS Mapper配置文件的systemId(用于兼容IBATIS) */ private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd"; /** * MYBATIS 全局配置文件的systemId */ private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd"; /** * MYBATIS Mapper配置文件的systemId */ private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd"; /** * MYBATIS全局配置文件对应的DTD文件 */ private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd"; /** * MYBATIS MAPPER 配置文件对应的DTD文件 */ private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd"; 复制代码
同时实现了 EntityResolver
的 resolveEntity
方法,在该方法中, XMLMapperEntityResolver
会为传入的 systemId
匹配一个对应的 DTD
验证文件:
/** * 从本地获取DTD文件 * * @param publicId 公共标志符号 * @param systemId 系统标志符 * @return DTD */ @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException { try { if (systemId != null) { String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH); if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) { // mybatis全局配置文件`config.xml`对应的DTD文件 return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId); } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) { // mybatis数据映射配置文件`*Mapper.xml`对应的DTD文件 return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId); } } return null; } catch (Exception e) { throw new SAXException(e.toString()); } } 复制代码
点击了解systemId和publicId
其实 EntityResolver
及其实现类 XMLMapperEntityResolver
是一个标准的策略模式实现。
策略模式是一种常见的行为性设计模式,作用:我们可以定义一系列的算法,并将这些算法封装起来,使他们可以互相替换,策略模式让算法可以独立于使用他们的客户端而独立变化.
因为我们在创建 sqlSessionFactory
对象时,传入的是配置文件的字节流,所以这里实际调用 XPathParser
的构造方法是 XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver)
(其余的构造方法和该方法代码类似):
/** * XPathParser * * @param inputStream XML文件输入流 * @param validation 是否进行DTD验证 * @param variables 用户自定义的属性对象,这些属性对象可以在整个配置文件中以占位符的形式被读取和使用 * @param entityResolver 自定义寻找DTD验证文件的解析器 */ public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { // 调用通用的构造方法 commonConstructor(validation, variables, entityResolver); // 将用户传入的XML文件流转换为Document对象 this.document = createDocument(new InputSource(inputStream)); } 复制代码
这个构造方法中的代码可以分为两个部分,一个是用于初始化属性的通用方法: commonConstructor
,另一个是用来解析获取XML文件对应的 Document
对象的方法: createDocument
.
其中 commonConstructor
方法的内容相对比较简单,唯一值得一提的就是为 xpath
属性硬编码赋值了 XPath
类型的对象实例,代码如下:
/** * 公共构造参数,主要是用来配置DocumentBuilderFactory * * @param validation 是否进行DTD校验 * @param variables 属性配置 * @param entityResolver 实体解析器 */ private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) { // 是否进行DTD校验 this.validation = validation; // 配置XML实体解析器 this.entityResolver = entityResolver; // 属性配置 this.variables = variables; // 初始化XPath XPathFactory factory = XPathFactory.newInstance(); this.xpath = factory.newXPath(); } 复制代码
createDocument
方法则主要是根据当前的配置解析 XML
文件获取对应的 Document
对象:
/** * 根据输入源,创建一个文本对象 * * @param inputSource 输入源 * @return 文本对象 */ private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 是否验证被解析的文档 factory.setValidating(validation); // 是否提供对XML命名空间的支持 factory.setNamespaceAware(false); //忽略注释 factory.setIgnoringComments(true); //忽略空白符 factory.setIgnoringElementContentWhitespace(false); //是否解析CDATA节点转换为TEXT节点 factory.setCoalescing(false); //是否展开实体引用节点 factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder(); // 配置XML文档的解析器,现在主要是XMLMapperEntityResolver builder.setEntityResolver(entityResolver); // 配置错误处理器 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 { } }); // 解析出DOM树 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } } 复制代码
在执行完这两个方法之后, XPathParser
的构造过程就已经结束了,这时候 XPathParser
不仅持有 Document
对象还持有一个 Xpath
访问器,接下来 XPathParser
的很多操作都会使用 Xpath
来读取 Document
对象中的内容。
下面是 XPathParser
对象的属性定义:
public class XPathParser { /** * XML对象 */ private final Document document; /** * 是否进行DTD验证 */ private boolean validation; /** * DTD实体解析器 */ private EntityResolver entityResolver; /** * 用户自定义的属性对象,这些属性对象可以在整个配置文件中以占位符的形式被读取和使用。 */ private Properties variables; /** * XML地址访问器 */ private XPath xpath; } 复制代码
到这里, XMLConfigBuilder
对象及其依赖的对象的构造过程基本上就完成了,接下来就是执行解析mybatis配置文件的工作了。