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;
}

到此,就结束了。

最新文章

  1. 10 个实用技巧,让 Finder 带你飞
  2. React(JSX语法)-----JSX基本语法
  3. linux原始套接字(1)-arp请求与接收
  4. ElasticSearch 2 (6) - 插件安装Head、Kopf与Bigdesk
  5. Mysql 获取当前时间函数 (类似于sql server 中的 getDate())
  6. 近期oepnfire工作总结.
  7. js判断input为空校验
  8. [转]Unity: make your lists functional with ReorderableList
  9. O2O模式成功案例分享 汲取精华化为己用
  10. hdu 3397 Sequence operation(很有意思的线段树题)
  11. android string[] arraylist&lt;string&gt;互转
  12. connectionStrings基本配置
  13. Oracle总结【SQL细节、多表查询、分组查询、分页】
  14. 动态规划----最长公共子序列(LCS)问题
  15. Kali学习笔记26:OWASP_ZAP
  16. SeaJS:一个适用于 Web 浏览器端的模块加载器
  17. tomcat server.xml
  18. php和NodeJs共存的开发环境
  19. 更优雅地关闭资源 - try-with-resource及其异常抑制
  20. 谷歌旗下专业图片编辑Snapseed获重大更新

热门文章

  1. ubuntu如何安装 adobe flash player或adobe插件
  2. 浅谈javascript的Touch事件
  3. 【NOI2019模拟】搬砖
  4. Integer判断大于 == 127时的坑
  5. 如何在tomcat前部署一个nginx
  6. JavaScript中浅拷贝和深拷贝的区别和实现
  7. Spring将Bean导入IOC容器
  8. 强化学习(三)—— 时序差分法(SARSA和Q-Learning)
  9. Android-SpinKit 进度条 (ProgressBar)
  10. mysql远程连接 Host * is not allowed to connect to this MySQL server