Mybatis源码之美:2.15.解析Mybatis的Mapper配置,初始化SQL环境的过程

706 阅读6分钟

解析Mybatis的Mapper配置,初始化SQL环境的过程

Mybatis主配置文件中定义的mappers元素,可以指定Mybatis加载Mapper对象的行为和方式。

mappers元素的DTD定义如下:

<!ELEMENT mappers (mapper*,package*)>

mappers元素中允许定义零个或多个mapperpackage子元素来进行Mapper对象的查找配置。

其中package子元素有一个必填的name属性,他指向一个基础包名,该包内所有符合条件的Mapper接口定义都会被注册到mybatis中。

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>

取自官方使用示例:

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

mapper子元素有三个具有互斥关系的属性定义,这三个属性必须有一个有值且不能同时存在。

mapper子元素的DTD定义如下:

<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>

他们的含义分别是:

  • resource表示使用相对类路径的资源(XML资源)引用
  • url表示使用完全限定资源定位符的资源(XML资源)引用
  • class表示使用映射器接口实现类的完全限定类名

取自官方使用示例:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

解析mappers元素的入口在XmlConfigBuilder#parseConfiguration(XNode root)方法中:

  // !!注册解析Dao对应的MapperXml文件
  mapperElement(root.evalNode("mappers"));

mapperElement方法定义如下:

/**
 * 解析配置文件中的mappers节点
 *
 * @param parent mappers节点内容
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 解析package元素,将包内的映射器接口实现全部注册为映射器
            // 比如:<package name="org.mybatis.builder"/>
            // 判断 configuration>mappers>package 属性
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                // 根据包名加载所有的DAO操作类并注册
                configuration.addMappers(mapperPackage);
            } else {
                // 解析mapper元素

                // 判断 configuration>mappers>mapper 属性
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    // 解析使用相对类路径的资源引用
                    // 比如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

                    // 只有resource
                    ErrorContext.instance().resource(resource);
                    // 读取指定的XML资源文件
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 继续解析Mapper Xml文件
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(
                            inputStream, /*XML文件输入流*/
                            configuration, /*用户全局配置*/
                            resource, /*资源配置文件的地址*/
                            configuration.getSqlFragments() /*已有的XML代码块*/
                    );
                    /*解析该XML文件*/
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    // 解析使用完全限定资源定位符的资源引用
                    // 比如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>

                    // 只有url
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    // 解析使用映射器接口实现类的完全限定类名
                    // 比如:<mapper class="org.mybatis.builder.AuthorMapper"/>

                    // 只有mapperClass 直接添加对象
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    // 不允许同时出现多条
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

方法实现很长,但是逻辑却比较简单,在获取到mappers元素的所有子节点之后,Mybatis会根据mappers子元素的类型和具体的属性配置来执行不同的逻辑。

其中package元素会交给Configuration#addMappers(String)来完成映射器的批量注册。

// 解析package元素,将包内的映射器接口实现全部注册为映射器
// 比如:<package name="org.mybatis.builder"/>
// 判断 configuration>mappers>package 属性
if ("package".equals(child.getName())) {
    String mapperPackage = child.getStringAttribute("name");
    // 根据包名加载所有的DAO操作类并注册
    configuration.addMappers(mapperPackage);
}

mapper元素则会根据具体配置的属性来执行不同的注册方式,配置class属性的mapper元素,会调用Configuration#addMapper(Class<T> type)方法来完成映射器的注册操作。

if (resource == null && url == null && mapperClass != null) {
    // 解析使用映射器接口实现类的完全限定类名
    // 比如:<mapper class="org.mybatis.builder.AuthorMapper"/>

    // 只有mapperClass 直接添加对象
    Class<?> mapperInterface = Resources.classForName(mapperClass);
    configuration.addMapper(mapperInterface);
} 

这种注册操作和package类似,都是通过解析Mapper接口作为依据来完成映射器的注册。

resourceurl属性的处理略有不同,他们的实现是基于Mapper接口对应的XML文件来完成的。

在具体的解析处理操作上,resourceurl的实现也比较类似,区别只在于加载XML文件的寻址方式不同而已。

  • resouce:
if (resource != null && url == null && mapperClass == null) {
    // 解析使用相对类路径的资源引用
    // 比如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

    // 只有resource
    ErrorContext.instance().resource(resource);
    // 读取指定的XML资源文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 继续解析Mapper Xml文件
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(
            inputStream, /*XML文件输入流*/
            configuration, /*用户全局配置*/
            resource, /*资源配置文件的地址*/
            configuration.getSqlFragments() /*已有的XML代码块*/
    );
    /*解析该XML文件*/
    mapperParser.parse();
}
  • url
if (resource == null && url != null && mapperClass == null) {
    // 解析使用完全限定资源定位符的资源引用
    // 比如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>

    // 只有url
    ErrorContext.instance().resource(url);
    InputStream inputStream = Resources.getUrlAsStream(url);
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(
            inputStream/*XML文件输入流*/
            , configuration/*用户全局配置*/
            , url/*资源配置文件的地址*/
            , configuration.getSqlFragments()/*已有的XML代码块*/
    );
    /*解析该XML文件*/
    mapperParser.parse();
}

仔细看上述resourceurl的处理代码,两者几乎完全一致,唯一的区别只在于加载文件的方式:

resource加载XML文件使用的是基于类加载路径的:InputStream inputStream = Resources.getResourceAsStream(resource);

url加载XML文件使用的是基于URL的:InputStream inputStream = Resources.getUrlAsStream(url);.

两者对于XML资源文件的加载都是委托给了Resources来实现,ResourcesMybatis封装的一个简化资源访问的工具类.

大致归类,我们可以将上诉四种解析方式分为两类: 一类是以Mapper接口作为依据,一类是以Mapper接口对应的XML文件作为依据。

package元素和mapper元素的class属性以Mapper接口作为依据。

他们的解析处理工作在Configuration中被委托给了名为MapperRegistry的对象来完成。

public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
}

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

mapper元素的resourceurl属性以Mapper接口对应的XML文件作为依据。

他们的解析处理工作被委托给了名为XMLMapperBuilder的对象来完成:

// 继续解析Mapper Xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
        inputStream, /*XML文件输入流*/
        configuration, /*用户全局配置*/
        resource, /*资源配置文件的地址*/
        configuration.getSqlFragments() /*已有的XML代码块*/
);
/*解析该XML文件*/
mapperParser.parse();

MapperRegistryMybatis中的映射器注册表,肩负着注册映射器的光荣任务。

XmlMapperBuilderBaseBuilder的一个实现,他的作用主要是用来解析Mybatismapper文件。

MapperRegistryXmlMapperBuilder的具体实现,我们后面会慢慢探索学习。

关注我,一起学习更多知识

关注我