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. 完美集群监控组合ganglia和nagios

    Ganglia是伯克利开发的一个集群监控软件.可以监视和显示集群中的节点的各种状态信息,比如如:cpu .mem.硬盘利用率, I/O负载.网络流量情况等,同时可以将历史数据以曲线方式通过php页面呈 ...

  2. 阿里中间件——消息中间件Notify和MetaQ

    3.1.Notify Notify是淘宝自主研发的一套消息服务引擎,是支撑双11最为核心的系统之一,在淘宝和支付宝的核心交易场景中都有大量使用.消息系统的核心作用就是三点:解耦,异步和并行.下面让我以 ...

  3. react中使用Ajax请求(axios,Fetch)

    React本身只关注于界面, 并不包含发送ajax请求的代码,前端应用需要通过ajax请求与后台进行交互(json数据),可以使用集成第三方ajax库(或自己封装) 常用的ajax请求库 jQuery ...

  4. CAS适用场景

    转载:http://www.jb51.net/article/86192.htm 下面小编就为大家带来一篇Java并发编程总结——慎用CAS详解.小编觉得挺不错的, 现在就分享给大家,也给大家做个参考 ...

  5. SQLite事务、错误与自动回滚

    BEGIN TRANSACTION begin-stmt: hide commit-stmt: hide rollback-stmt: hide No changes can be made to t ...

  6. python抓取月光博客的全部文章而且依照标题分词存入mongodb中

    猛击这里:python抓取月光博客的全部文章

  7. java中的多态是怎么体现的

    多态是父类的引用指向了自己的子类对象. 当调用方法时,会根据对象去调用方法,先在子类中找,没有就去父类中找 总结:成员变量是在编译阶段绑定的,方法时在运行阶段绑定的.属性不能重写,方法可以重写. pu ...

  8. C#定时备份正在播放的幻灯片、word文档、excel电子表格,mht格式文档

    控制台应用, 代码如下: using System; using System.Collections.Generic; using System.IO; using System.Linq; usi ...

  9. 【angularjs】使用angularjs模拟淘宝首页-淘宝头条滚动效果

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. 01 python初学(注释、交互、if while for)

    为了能生存下去,一定要坚持学习! 目录  1. 注释 2. 用户交互 3. if .while.for 语句 1. 注释  单行注释: # 多行注释: 三个单引号 || 三个双引号 2. 用户交互: ...