在上篇文章中分析了mybatis解析mapper标签中的resource、url属性的过程,《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》。通过分析可以知道在解析这两个属性的时候首先解析的是对应的XML映射文件,然后解析XML映射文件中的namespace属性配置的接口,在上篇中说到该解析过程和mapper标签中的class属性的解析过程是一样的,因为class属性配置的即是一个接口的全限类名。

一、概述

在mybatis的核心配置文件中配置mappers标签有以下方式,

<mappers>
<mapper class="cn.com.mybatis.dao.UserMapper"/>
</mappers>

上面这种方式便是mapper标签的class属性配置方式,其解析部分过程如下,

else if (resource == null && url == null && mapperClass != null) {
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.");
}

可以看到主要是调用了configuration.addMapper方法,和上篇文章中解析namespace调用的方法是一致的。看其具体实现

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

下方分析mapperRegistry.addMapper方法。

二、详述

mapperRegistry.addMapper方法的定义如下,

 public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//判断是否为接口
if (hasMapper(type)) {//如果knownMappers中已经存在该type,则抛出异常
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//1、把type放入knownMappers中,其value为一个MapperProxyFactory对象
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量
      //具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
        /**sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
*
*/
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {//3、如果解析失败,则删除knowMapper中的信息
knownMappers.remove(type);
}
}
}
}

该方法主要分为下面几个步骤。

1、检查是否解析过接口

首先会判断knowMappers中是否已经存在该接口,如果存在则会抛出异常

if (hasMapper(type)) {//如果knownMappers中已经存在该type,则抛出异常
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}

如果不存在则放入knownMappers中,

 //1、把type放入knownMappers中,其value为一个MapperProxyFactory对象
knownMappers.put(type, new MapperProxyFactory<T>(type));

继续解析对应的映射文件及接口方法注解

2、解析接口对应的映射文件及接口方法注解

上面把mapper接口放入了knownMappers中,接着需要解析映射文件及注解,

//2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量
//具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
/**sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
*
*/
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse();
loadCompleted = true;

上面的代码,生成了一个MapperAnnotationBuilder实例,

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type; sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}

给sqlAnnotationTypes和sqlProviderAnnotationTypes进行了赋值。

下面看具体的解析过程,

parser.parse();

MapperAnnotationBuilder的parse方法如下,

public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {//判断是否加载过该Mapper接口
//解析和接口同名的xml文件,前提是存在该文件,如果不存在该文件要怎么解析那?答案是解析接口中方法上的注解
/**
* 1、解析和接口同名的xml配置文件,最终要做的是把xml文件中的标签,转化为mapperStatement,
* 并放入mappedStatements中
*
*/
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析接口上的@CacheNamespace注解
parseCache();
parseCacheRef();
//2、获得接口中的所有方法,并解析方法上的注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析方法上的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}

首先判断是否加载过该资源,

if (!configuration.isResourceLoaded(resource)) {

}

只有未加载过,才会执行该方法的逻辑,否则该方法执行完毕。

public boolean isResourceLoaded(String resource) {
return loadedResources.contains(resource);
}

从loadResources中进行判断,判断是否解析过该Mapper接口,答案是没有解析过,则会继续解析。

1.1、解析对应的XML文件

首先会解析XML文件,调用下面的方法,

//解析和接口同名的xml文件,前提是存在该文件,如果不存在该文件要怎么解析那?答案是解析接口中方法上的注解
/**
* 1、解析和接口同名的xml配置文件,最终要做的是把xml文件中的标签,转化为mapperStatement,
* 并放入mappedStatements中
*
*/
loadXmlResource();

看loadXmlResource方法

/**
* 解析mapper配置文件
*/
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//解析xml映射文件
xmlParser.parse();
}
}
}

首先进行了判断,进入if判断,看判断上的注解

// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace

第一句注解没理解什么意思,第二句的意思是方式两次加载资源,第三句是说明了该标识是在XMLMapperBuilder类中的bindMapperForNamespace中进行的设置,如下

为什么这样设置,后面会总结mapper的加载流程详细说明该问题。

判断之后寻找相应的XML映射文件,映射文件的文件路径如下,

//解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
String xmlResource = type.getName().replace('.', '/') + ".xml";

从上面可以看出Mapper接口文件和XML映射文件在同一个包下,且文件名称相同(扩展名不同)。接着便是解析XML映射文件的逻辑。

if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//解析xml映射文件
xmlParser.parse();
}

该逻辑和《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》的过程是一样的,调用XMLMapperBuilder的parse方法进行解析,解析的结果为MapperStatement对象。

1.2、解析接口方法上的注解

上面是解析接口对应的XML映射文件,解析完成之后,还要解析接口方法上的注解,因为mybatis的sql配置有两种方式,一种是通过XML映射文件,另一种便是注解(当SQL比较复杂建议使用映射文件的方式),下面看解析注解的过程,

//2、获得接口中的所有方法,并解析方法上的注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析方法上的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}

通过反射的方式获得接口中的所有方法,遍历方法执行parseStatement方法

void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
//获得方法上的注解,并生成SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
//生成mappedStatementId,为接口的权限类名+方法名。从这里可以得出同一个接口或namespace中不允许有同名的方法名或id
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect; KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
} if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
} String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
} assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}

注解的解析和解析XML映射文件的方式是一样的,解析的属性是一致的。需要注意下面的注解

@SelectProvider(type=BaseUserProvider.class,method="getUser")

该注解的意思是定义select语句的提供者,需要配置type和method,即提供类的Class对象和相应的方法(返回一个字符串)

3、解析失败回退

如果在继续过程中失败或抛出异常,则进行回退,回退的意思是从knownMappers中删除该类型。

finally {
if (!loadCompleted) {//3、如果解析失败,则删除knowMapper中的信息
knownMappers.remove(type);
}
}

因为Mapper解析的过程有两个结果一个是放入到configuration.knownMappers中的MapperProxyFactory对象,一个是放入到configuration.mappedStatements中MappedStatement对象,由于生产MappedStatement对象失败,所以要回退生成MapperProxyFactory对象过程。

三、总结

本文分析了mybatis解析<mapper class=""/>的过程,依旧是包含MapperProxyFactory和MappedStatement两个过程。

有不当之处,欢迎指正,感谢!

mybatis源码配置文件解析之五:解析mappers标签(解析class属性)的更多相关文章

  1. mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)

    在上篇文章中分析了mybatis解析<mappers>标签,<mybatis源码配置文件解析之五:解析mappers标签>重点分析了如何解析<mappers>标签中 ...

  2. mybatis源码配置文件解析之五:解析mappers标签

    在上篇博客中分析了plugins标签,<mybatis源码配置文件解析之四:解析plugins标签>,了解了其使用方式及背后的原理.现在来分析<mappers>标签. 一.概述 ...

  3. mybatis源码配置文件解析之二:解析settings标签

    在前边的博客中分析了mybatis解析properties标签,<mybatis源码配置文件解析之一:解析properties标签>.下面来看解析settings标签的过程. 一.概述 在 ...

  4. mybatis源码配置文件解析之三:解析typeAliases标签

    在前边的博客在分析了mybatis解析settings标签,<mybatis源码配置文件解析之二:解析settings标签>.下面来看解析typeAliases标签的过程. 一.概述 在m ...

  5. mybatis源码配置文件解析之四:解析plugins标签

    在前边的博客在分析了mybatis解析typeAliases标签,<mybatis源码配置文件解析之三:解析typeAliases标签>.下面来看解析plugins标签的过程. 一.概述 ...

  6. mybatis源码配置文件解析之五:解析mappers标签流程图

    前面几篇博客分析了mybatis解析mappers标签的过程,主要分为解析package和mapper子标签.补充一张解析的总体过程流程图,画的不好,多多谅解,感谢.

  7. mybatis源码配置文件解析之一:解析properties标签

    mybatis作为日常开发的常用ORM框架,在开发中起着很重要的作用,了解其源码对日常的开发有很大的帮助.源码版本为:3-3.4.x,可执行到github进行下载. 从这篇文章开始逐一分析mybati ...

  8. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  9. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  10. mybatis源码-解析配置文件(四)之配置文件Mapper解析

    在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...

随机推荐

  1. .NET Core Configuration 配置项知识点一网打尽!

    控制台项目中,演示示例 1.自定义 Dictionary Config  内存字典模式 dotnet add package Microsoft.Extensions.Configuration IC ...

  2. kettle从入门到精通 第三十一课 mysql 数据连接连接池配置

    无论开发应用程序还是做ETL研发,都离不开连接池的应用,如下是kettle中mysql 连接池设置界面,今天重点讲解下连接池中的参数配置. defaultAutoCommit 当 defaultAut ...

  3. FFmpeg开发笔记(二十八)Linux环境给FFmpeg集成libxvid

    ​XviD是个开源的视频编解码器,它与DivX一同被纳入MPEG-4规范第二部分的视频标准,但DivX并未开源.早期的MP4视频大多采用XviD或者DivX编码,当时的视频格式被称作MPEG-4.现在 ...

  4. java: 找不到符号 java: Compilation failed: internal java compiler error

    java: 找不到符号 java: Compilation failed: internal java compiler error 1.File---->Setting------>ja ...

  5. Vue学习:18.Vue插槽

    Vue 中的插槽(slot)是一种灵活的机制,用于在父组件中将内容传递到子组件的特定位置.它允许我们在子组件中定义可以在父组件中传递任意内容的"插槽",从而实现更灵活的组件化. 在 ...

  6. 如何基于R包做GO分析?实现秒出图

    GO分析 基因本体论(Gene Ontology, GO)是一个用于描述基因和基因产品属性的标准术语体系.它提供了一个有组织的方式来表示基因在生物体内的各种角色.基因本体论通常从三个层面对基因进行描述 ...

  7. PasteSpider的集群组件PasteCluster(让你的项目快速支持集群模式)的思路及实现(含源码)

    PasteSpider是什么? 一款使用.net编写的开源的Linux容器部署助手,支持一键发布,平滑升级,自动伸缩, Key-Value配置,项目网关,环境隔离,运行报表,差量升级,私有仓库,集群部 ...

  8. 地铁查询app 结对作业三

    经过今天一下午的奋斗 安卓app 只剩下最难的部分了 最短路径问题 我们考虑用迪杰斯特拉算法 不过 没有做出来 还要继续去学习一下这个代码 并寻求网上代码的帮助

  9. python pika rabbitmq demo

    import pika import json # https://www.cnblogs.com/zhaohuaxishi/p/12107392.html # https://www.cnblogs ...

  10. http请求方式-HttpURLConnection

    http请求方式-HttpURLConnection import com.alibaba.fastjson.JSON; import com.example.core.mydemo.http.Ord ...