转载

Mybatis源码之美:2.1.构建XmlConfigBuilder,准备解析XML文件的基础环境

前面说过, 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 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

  • 脚本语言驱动器: 点击了解脚本语言驱动器,相关内容在标题为"动态 SQL 中的可插拔脚本语言"段落内

可以查看简单的实例测试: 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定义了 默认的脚本语言驱动器 是别名为 XMLXMLLanguageDriver

我们之前说过 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 对象的引用,同时把自身的 typeHandlerRegistryTypeAliasRegistry 属性指向了 Configuration 对象的 typeHandlerRegistryTypeAliasRegistry 属性。

public class Configuration {
  ...
/**
    * 类型处理器注册表
    */
   protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
   /**
    * 类型别名注册表,主要用在执行SQL语句的出入参以及一些类的简写
    */
   protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
   ...
 }
复制代码
  • 类型处理器: 点击了解mybatis类型处理器

无论是 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对象都可以。

Mybatis源码之美:2.1.构建XmlConfigBuilder,准备解析XML文件的基础环境

可选传的参数有三个:

  • 第一个是 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";
    
    复制代码

    同时实现了 EntityResolverresolveEntity 方法,在该方法中, 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

    • systemId: 系统标志符,通常用于直接指出对应DTD资源的实际位置,往往是应用程序范围内唯一的。
    • publicId: 公共标志符,是一个全球唯一的标志,通常与特定的DTD关联,间接的指出DTD文件的位置,通常用于跨多个应用的场景。

    其实 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配置文件的工作了。

原文  https://juejin.im/post/5e9668b2f265da47d83009ad
正文到此结束
Loading...