mybatis两种开发方式这边文章中,我们提到了Mapper动态代理开发这种方式,现在抛出一个问题:通过sqlSession.getMapper(XXXMapper.class)来获取代理对象的过程是怎样的?生成的代理对象是通过怎样的方式来调用Mapper接口指定的方法的?

我们根据源码来一步步分析:

首先进入getMapper方法,通过一步步追踪,我们可以进入到MapperRegistry类中的getMapper方法,现在对整个类做分析:

/**
* @author Clinton Begin
* @author Eduardo Macarron
* @author Lasse Voss
*/ // MapperRegistry整个类的作用就是注册Mapper接口和生成Mapper接口的代理对象
public class MapperRegistry {
// 配置文件对象
private Configuration config;
// 这是一个HashMap,key是Mapper接口的类型对象,value是MapperProxyFactory
// 至于MapperProxyFactory是一个创建Mapper接口代理对象的工厂,后面会介绍
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public MapperRegistry(Configuration config) {
this.config = config;
}

// 整个就是生成代理对象的方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口的类型对象获取对应的 生成该Mapper接口代理对象的工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
// 这个就是生成代理对象的方法 进入该方法
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
} public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}

// 注册Mapper接口
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 把Mapper接口的类型对象和MapperProxyFactory对象,放到HashMap中
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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
} /**
* @since 3.2.2
*/
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
} /**
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
} /**
* @since 3.2.2
* 通过包名扫描其下的所有Mapper接口
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}

然后进入 mapperProxyFactory.newInstance(sqlSession);这个方法,

// 该类就是用来生成Mapper接口的代理对象的工厂
public class MapperProxyFactory<T> {
// Mapper接口的class对象
private final Class<T> mapperInterface;
// key是Method方法对象,MapperMethod是对Mapper接口中方法的封装,这个MapperMethod值得探究,后面会有讲解
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过JDK动态代理的方式生成了Mapper接口的代理对象,所以当代理对象调用Mapper接口的方法时,会触发mapperProxy对象中的invoke方法,下面对MapperProxy进行分析 (重点)
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}


public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }

贴出MapperProxy类的源码:

// 该类实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

// 代理对象调用Mapper接口中的方法时,会触发该方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 通过方法名找到MapperMethod,MapperMethod是对方法及其他参数的封装,稍后会提到
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 此处是对SqlSession调用的封装,在另外一篇文章中,我们提到了Mapper动态代理的方法底层还是通过SQLSession来和数据库交互的,在这里就能看出来了(核心代码就在这个方法中)
return mapperMethod.execute(sqlSession, args);
}


private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
} }

进入上面提到的方法,这里只贴出了MapperMethod类的属性和部分方法:

public class MapperMethod {
// SqlCommand是对方法的全名(方法的全名 = 包名+类名+方法名(也就是Mapper.xml中的id))和操作类型(Mapper.xml中的insert,select,update等)的封装
private final SqlCommand command;
// MethodSignature 封装了方法的参数,返回类型等信息
private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
// 核心逻辑在这里
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
// 对参数做处理
Object param = method.convertArgsToSqlCommandParam(args);
// 通过sqlSession.insert(方法全名,param)这种方式来和数据库交互,并对返回的结果做处理,以下都是类似的情况:update,delete,select
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
// 这个是查询 sqlSession.select()....
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}

到这里我们明白了一点:通过Mapper动态代理的方式使用mybatis,其实就是利用了JDK的动态代理特性来生成对象,然后通过反射的方式来调用方法,最终底层还是通过mybatis提供的API sqlSession来和数据库进行交互的。

最后,再对SqlCommand 和 MethodSignature做一个简单的分析:

SqlCommand类有两个属性:name和type分别代表什么呢?通过例子来理解

 public static class SqlCommand {

    private final String name;  // com.yht.mybatisTest.dao.GoodsDao.selectGoodsById  方法的全名
private final SqlCommandType type; //SELECT Mapper.xml文件中节点的类型:SELECT,INSERT,DELETE或者UPDATE
}

MethodSignature类的属性如下:

public static class MethodSignature {

    private final boolean returnsMany; //是否返回多条数据
private final boolean returnsMap; //是否返回Map数据类型
private final boolean returnsVoid; // 是否返回void
private final Class<?> returnType; // 具体的返回的数据对象类型,如:com.yht.mybatisTest.entity.Goods
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final SortedMap<Integer, String> params; // 参数
private final boolean hasNamedParameters;
}

到此,就结束了。

mybatis源码分析(四)---------------代理对象的生成的更多相关文章

  1. Mybatis源码解析 - mapper代理对象的生成,你有想过吗

    前言 开心一刻 本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶紧摘了吧.我逗他:那你好好学习,以后给老师买个漂亮的?这孩子想都没想立刻回答:等我赚钱了,带 ...

  2. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  3. Springboot源码分析之代理对象内嵌调用

    摘要: 关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法.首先在这儿我要声明事务直接的嵌套调 ...

  4. MyBatis 源码分析——动态代理

    MyBatis框架是如何去执行SQL语句?相信不只是你们,笔者也想要知道是如何进行的.相信有上一章的引导大家都知道SqlSession接口的作用.当然默认情况下还是使用DefaultSqlSessio ...

  5. jquery源码分析(四)——回调对象 Callbacks

    借用百度百科来说明下回调函数: 回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该 ...

  6. 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  8. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

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

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

  10. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

随机推荐

  1. orcFile split和读数据原理总结(hive0.13)

    http://blog.csdn.net/zhaorongsheng/article/details/72903431 官网关于orcfile的介绍 背景 Hive的rcfile格式已经使用多年,但是 ...

  2. 直接运行vue+django项目

    直接运行vue+django项目 下载前后端代码 wget https://files.cnblogs.com/files/pyyu/luffy_boy.zip wget https://files. ...

  3. SQL UPDATE 语句

    Update 语句 Update 语句用于修改表中的数据. 语法: UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值 Person: LastName FirstName ...

  4. 路飞学城-Python开发集训-第4章

    学习心得: 学习笔记: 在python中一个py文件就是一个模块 模块好处: 1.提高可维护性 2.可重用 3.避免函数名和变量名冲突 模块分为三种: 1.内置标准模块(标准库),查看所有自带和第三方 ...

  5. 06 python初学 (列表内置方法)

    目录: type(a) is list :判断 a 是不是列表.返回 True False count:计算列表内某一元素出现的次数 extend:在列表末尾一次性添加另一列表中的全部值 index: ...

  6. Linux进程管理 (7)实时调度

    关键词:RT.preempt_count.RT patch. 除了CFS调度器之外,还包括重要的实时调度器,有两种RR和FIFO调度策略.本章只是一个简单的介绍. 更详细的介绍参考<Linux进 ...

  7. python logging详解及自动添加上下文信息

    之前写过一篇文章日志的艺术(The art of logging),提到了输出日志的时候记录上下文信息的重要性,我认为上下文信息包括: when:log事件发生的时间 where:log事件发生在哪个 ...

  8. UVA10256 The Great Divide

    怎么又没人写题解,那我来贡献一发好了. 题目意思很简单,平面上有两种颜色的点,问你能否求出一条直线使两种颜色的点完全分开. 首先我们考虑两个点集相离的充要条件,这两个点集的凸包必须相离.(很好证明或者 ...

  9. en

    发音,这个应该算是学习英语的头等大事,如果没有机会和条件练好发音,也可以先将就着,不过后面你就会感觉到你说的人家可能会听不懂,我自己也曾经深受其害. 基本常用单词积累(大概2000~4000左右的词汇 ...

  10. c++学习之字符串拼接

    在这里,强调这样一个问题: 可以看出,c++中,定义了string类,string 类方便我们进行字符串的一些操作,而不是像C语言中采用字符数组的方式或者指针的方式,通过上面的一些描述,可以发现: s ...